diff --git a/CHANGELOG.md b/CHANGELOG.md
index f262753da048b..af147fb283c55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Change Log
+## v1.13.0
+
+- [core] add API to filter contributions at runtime [#9317](https://github.com/eclipse-theia/theia/pull/9317) Contributed on behalf of STMicroelectronics
+
+[Breaking Changes:](#breaking_changes_1.13.0)
+
## v1.12.0 - 3/25/2020
[1.12.0 Milestone](https://github.com/eclipse-theia/theia/milestone/17)
diff --git a/examples/api-samples/src/browser/api-samples-frontend-module.ts b/examples/api-samples/src/browser/api-samples-frontend-module.ts
index d06b8fb0ea21e..10a3ba15d7ecc 100644
--- a/examples/api-samples/src/browser/api-samples-frontend-module.ts
+++ b/examples/api-samples/src/browser/api-samples-frontend-module.ts
@@ -16,6 +16,7 @@
import { ContainerModule } from '@theia/core/shared/inversify';
import { bindDynamicLabelProvider } from './label/sample-dynamic-label-provider-command-contribution';
+import { bindSampleFilteredCommandContribution } from './contribution-filter/sample-filtered-command-contribution';
import { bindSampleUnclosableView } from './view/sample-unclosable-view-contribution';
import { bindSampleOutputChannelWithSeverity } from './output/sample-output-channel-with-severity';
import { bindSampleMenu } from './menu/sample-menu-contribution';
@@ -31,4 +32,5 @@ export default new ContainerModule(bind => {
bindSampleMenu(bind);
bindSampleFileWatching(bind);
bindVSXCommand(bind);
+ bindSampleFilteredCommandContribution(bind);
});
diff --git a/examples/api-samples/src/browser/contribution-filter/sample-filtered-command-contribution.ts b/examples/api-samples/src/browser/contribution-filter/sample-filtered-command-contribution.ts
new file mode 100644
index 0000000000000..5ea30b68ec5c9
--- /dev/null
+++ b/examples/api-samples/src/browser/contribution-filter/sample-filtered-command-contribution.ts
@@ -0,0 +1,50 @@
+/********************************************************************************
+ * Copyright (C) 2021 STMicroelectronics and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+import { Command, CommandContribution, CommandRegistry, ContributionFilter, equals, NameBasedContributionFilter } from '@theia/core/lib/common';
+import { injectable, interfaces } from '@theia/core/shared/inversify';
+export namespace SampleFilteredCommand {
+ const EXAMPLE_CATEGORY = 'Examples';
+ export const FILTERED: Command = {
+ id: 'example_command.filtered',
+ category: EXAMPLE_CATEGORY,
+ label: 'This command should be filtered out'
+ };
+}
+
+/**
+ * This sample command is used to test the runtime filtering of already bound contributions.
+ */
+@injectable()
+export class SampleFilteredCommandContribution implements CommandContribution {
+
+ registerCommands(commands: CommandRegistry): void {
+ commands.registerCommand(SampleFilteredCommand.FILTERED, { execute: () => { } });
+ }
+}
+
+@injectable()
+export class SampleFilteredCommandContributionFilter extends NameBasedContributionFilter {
+
+ contributions = [CommandContribution];
+ doTest(toTest: string): boolean {
+ return equals(toTest, false, 'SampleFilteredCommandContribution');
+ }
+}
+
+export const bindSampleFilteredCommandContribution = (bind: interfaces.Bind) => {
+ bind(CommandContribution).to(SampleFilteredCommandContribution);
+ bind(ContributionFilter).to(SampleFilteredCommandContributionFilter);
+};
diff --git a/examples/api-tests/src/contribution-filter.spec.js b/examples/api-tests/src/contribution-filter.spec.js
new file mode 100644
index 0000000000000..c5267ed4496e4
--- /dev/null
+++ b/examples/api-tests/src/contribution-filter.spec.js
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * Copyright (C) 2021 STMicroelectronics and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+// @ts-check
+describe('Contribution filter', function () {
+ this.timeout(5000);
+ const { assert } = chai;
+
+ const { CommandRegistry, CommandContribution } = require('@theia/core/lib/common/command');
+ const { SampleFilteredCommandContribution, SampleFilteredCommand } = require('@theia/api-samples/lib/browser/contribution-filter/sample-filtered-command-contribution');
+
+ const container = window.theia.container;
+ const commands = container.get(CommandRegistry);
+
+ it('filtered command in container but not in registry', async function () {
+ const allCommands = container.getAll(CommandContribution);
+ assert.isDefined(allCommands.find(contribution => contribution instanceof SampleFilteredCommandContribution),
+ 'SampleFilteredCommandContribution is not bound in container');
+ const filteredCommand = commands.getCommand(SampleFilteredCommand.FILTERED.id);
+ assert.isUndefined(filteredCommand, 'SampleFilteredCommandContribution should be filtered out but is present in "CommandRegistry"');
+ });
+
+});
diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts
index 5aad42ea55905..602bd63a59911 100644
--- a/packages/core/src/browser/frontend-application-module.ts
+++ b/packages/core/src/browser/frontend-application-module.ts
@@ -97,6 +97,7 @@ import { LanguageService } from './language-service';
import { EncodingRegistry } from './encoding-registry';
import { EncodingService } from '../common/encoding-service';
import { AuthenticationService, AuthenticationServiceImpl } from '../browser/authentication-service';
+import { ContributionFilterRegistry } from '../common/contribution-filter';
export { bindResourceProvider, bindMessageService, bindPreferenceService };
@@ -342,4 +343,7 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
bind(ContextMenuContext).toSelf().inSingletonScope();
bind(AuthenticationService).to(AuthenticationServiceImpl).inSingletonScope();
+
+ bind(ContributionFilterRegistry).toSelf().inSingletonScope();
+
});
diff --git a/packages/core/src/common/contribution-filter/contribution-filter-registry.ts b/packages/core/src/common/contribution-filter/contribution-filter-registry.ts
new file mode 100644
index 0000000000000..417108edf837b
--- /dev/null
+++ b/packages/core/src/common/contribution-filter/contribution-filter-registry.ts
@@ -0,0 +1,69 @@
+/********************************************************************************
+ * Copyright (C) 2021 STMicroelectronics and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { injectable, multiInject, optional } from 'inversify';
+import { ContributionFilter, ContributionType } from './contribution-filter';
+import { applyFilters } from './filter';
+
+const GENERIC_CONTRIBUTION_FILTER_KEY = '*';
+@injectable()
+export class ContributionFilterRegistry {
+ registry: Map;
+ constructor(@multiInject(ContributionFilter) @optional() contributionFilters: ContributionFilter[] = []) {
+ this.registry = new Map();
+ contributionFilters.forEach(filter => {
+ if (!filter.contributions || filter.contributions.length === 0) {
+ this.addFilter(GENERIC_CONTRIBUTION_FILTER_KEY, filter);
+ } else {
+ filter.contributions.forEach(type => {
+ this.addFilter(type, filter);
+ });
+ }
+ });
+ }
+
+ private addFilter(type: ContributionType, filter: ContributionFilter): void {
+ this.getOrCreate(type).push(filter);
+ }
+
+ private getOrCreate(type: ContributionType): ContributionFilter[] {
+ let value = this.registry.get(type);
+ if (!value) {
+ value = [];
+ this.registry.set(type, value);
+ }
+ return value;
+ }
+
+ get(type: ContributionType): ContributionFilter[] {
+ const filters = [...(this.registry.get(type) || [])];
+ if (type !== GENERIC_CONTRIBUTION_FILTER_KEY) {
+ filters.push(...(this.registry.get(GENERIC_CONTRIBUTION_FILTER_KEY) || []));
+ }
+ return filters;
+ }
+
+ /**
+ * Applies the filters for the given contribution type. Generic filters will be applied on any given type.
+ * @param toFilter the elements to filter
+ * @param type the contribution type for which potentially filters were registered
+ * @returns the filtered elements
+ */
+ applyFilters(toFilter: T[], type: ContributionType): T[] {
+ const filters = this.get(type);
+ return applyFilters(toFilter, filters);
+ }
+}
diff --git a/packages/core/src/common/contribution-filter/contribution-filter.ts b/packages/core/src/common/contribution-filter/contribution-filter.ts
new file mode 100644
index 0000000000000..b423e015a6bbc
--- /dev/null
+++ b/packages/core/src/common/contribution-filter/contribution-filter.ts
@@ -0,0 +1,40 @@
+/********************************************************************************
+ * Copyright (C) 2021 STMicroelectronics and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { interfaces } from 'inversify';
+import { Filter } from './filter';
+import { NameBasedFilter } from './string-based-filter';
+export type ContributionType = interfaces.ServiceIdentifier;
+
+export const ContributionFilter = Symbol('ContributionFilter');
+
+/**
+ * Specialized `Filter` that is used by the `ContainerBasedContributionProvider` to
+ * filter unwanted contributions that are already bound in the DI container.
+ */
+export interface ContributionFilter extends Filter