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.

- Update the `cpp` manager to handle the new data structure.
- Update the test cases (fix the task test case with incorrect imports and false positive results).

Signed-off-by: Vincent Fugnitto <vincent.fugnitto@ericsson.com>
Signed-off-by: Paul Maréchal <paul.marechal@ericsson.com>
  • Loading branch information
vince-fugnitto authored and paul-marechal committed Apr 5, 2019
1 parent 9fdc224 commit 2668602
Show file tree
Hide file tree
Showing 14 changed files with 519 additions and 171 deletions.
17 changes: 9 additions & 8 deletions packages/cpp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@
"@theia/monaco": "^0.5.0",
"@theia/preferences": "^0.5.0",
"@theia/process": "^0.5.0",
"@theia/workspace": "^0.5.0",
"@theia/task": "^0.5.0",
"string-argv": "^0.1.1"
"string-argv": "^0.1.1",
"fs-extra": "^4.0.2"
},
"publishConfig": {
"access": "public"
},
"theiaExtensions": [
{
"frontend": "lib/browser/cpp-frontend-module",
"backend": "lib/node/cpp-backend-module"
}
],
"theiaExtensions": [{
"frontend": "lib/browser/cpp-frontend-module",
"backend": "lib/node/cpp-backend-module"
}],
"keywords": [
"theia-extension"
],
Expand All @@ -47,7 +47,8 @@
"test": "theiaext test"
},
"devDependencies": {
"@theia/ext-scripts": "^0.5.0"
"@theia/ext-scripts": "^0.5.0",
"@types/fs-extra": "^4.0.2"
},
"nyc": {
"extends": "../../configs/nyc.json"
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;
}

}
156 changes: 89 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,88 +42,109 @@ 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).withoutScheme().toString() : undefined;
/**
* 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> {

// Prompt users to determine working root.
const root = await this.selectWorkspaceRoot();
if (!root) {
return;
}

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

// Only return 'Create New' when no build configurations present
if (!configurations.length) {
return acceptor(items);
// 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);
}
}

protected async selectWorkspaceRoot(): Promise<string | undefined> {
const roots = this.workspaceService.tryGetRoots();
return this.quickPick.show(roots.map(
({ uri }) => ({
label: this.labelProvider.getName(new URI(uri).withoutScheme()),
value: uri,
description: this.cppBuildConfigurations.getActiveConfig(uri)
? this.cppBuildConfigurations.getActiveConfig(uri)!.name
: 'undefined'
})
), { placeholder: 'Select workspace root' });
}

// Item to de-select any active build config
if (active) {
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);
}

// Add one item per build config
configurations.forEach(config => {
const uri = new URI(config.directory);
items.push(new QuickOpenItem({
// 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,
// 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;
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
},
}));
});

acceptor(items);
}

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,
});
});
return this.quickPick.show(items, { placeholder: 'Select action' });
}

/** Create a new build configuration with placeholder values. */
Expand All @@ -131,7 +154,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 @@ -183,7 +205,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 2668602

Please sign in to comment.