Skip to content

Commit

Permalink
Provide API to filter unwanted contributions
Browse files Browse the repository at this point in the history
Adds the ContributionFilterRegistry which can be used to filter
contributions of Theia extensions before they are bound.

This mechanism can be used by application developers to specifically
filter individual contributions from Theia extensions they have no
control over, i.e. core or 3rd party extensions.

Resolves eclipse-theia#9069

Contributed on behalf of STMicroelectronics

Signed-off-by: Tobias Ortmayr <tortmayr@eclipsesource.com>
  • Loading branch information
tortmayr authored and sdirix committed Apr 8, 2021
1 parent fd91f21 commit deeaa16
Show file tree
Hide file tree
Showing 13 changed files with 393 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## v1.13.0

- [core] add API to filter contributions at runtime [#XXXX](https://github.com/eclipse-theia/theia/pull/XXXX) Contributed on behalf of STMicroelectronics

<a name="breaking_changes_1.13.0">[Breaking Changes:](#breaking_changes_1.13.0)</a>

## v1.12.0 - 3/25/2020

[1.12.0 Milestone](https://github.com/eclipse-theia/theia/milestone/17)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -31,4 +32,5 @@ export default new ContainerModule(bind => {
bindSampleMenu(bind);
bindSampleFileWatching(bind);
bindVSXCommand(bind);
bindSampleFilteredCommandContribution(bind);
});
Original file line number Diff line number Diff line change
@@ -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);
};
36 changes: 36 additions & 0 deletions examples/api-tests/src/contribution-filter.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/********************************************************************************
* Copyright (C) 2021 TypeFox 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"');
});

});
4 changes: 4 additions & 0 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down Expand Up @@ -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();

});
Original file line number Diff line number Diff line change
@@ -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<ContributionType, ContributionFilter[]>;
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<T extends Object>(toFilter: T[], type: ContributionType): T[] {
const filters = this.get(type);
return applyFilters<T>(toFilter, filters);
}
}
Original file line number Diff line number Diff line change
@@ -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<unknown>;

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<Object> {
/**
* Contribution types for which this filter is applicable. If `undefined` or empty this filter
* will be applied to all contribution types.
*/
contributions?: ContributionType[];
}

/**
* Specialized `ContributionFilter` that can be used to filter contributions based on their constructor name.
*/
export abstract class NameBasedContributionFilter extends NameBasedFilter<Object> implements ContributionFilter {
}
48 changes: 48 additions & 0 deletions packages/core/src/common/contribution-filter/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/********************************************************************************
* 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
********************************************************************************/

export const Filter = Symbol('Filter');
/**
* A `Filter` can be used to test whether a given object should be filtered
* from a set of objects. The `test` function can be applied to an object
* of matching type and returns `true` if the object should be filtered out.
*/
export interface Filter<T extends Object> {
/**
* Evaluates this filter on the given argument.
* @param toTest Object that should be tested
* @returns `true` if the object should be filtered out, `false` otherwise
*/
test(toTest: T): Boolean;
}

/**
* Applies a set of filters to a set of given objects and returns the set of filtered objects.
* @param toFilter Set of objects which should be filtered
* @param filters Set of filters that should be applied
* @param negate Negation flag. If set to true the result of all `Filter.test` methods is negated
* @returns The set of filtered arguments
*/
export function applyFilters<T extends Object>(toFilter: T[], filters: Filter<T>[], negate: boolean = false): T[] {
if (filters.length === 0) {
return toFilter;
}
return toFilter.filter(object => {
const result = filters.every(filter => !filter.test(object));
return negate ? !result : result;
});
}

20 changes: 20 additions & 0 deletions packages/core/src/common/contribution-filter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/********************************************************************************
* 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
********************************************************************************/

export * from './contribution-filter';
export * from './contribution-filter-registry';
export * from './filter';
export * from './string-based-filter';
Loading

0 comments on commit deeaa16

Please sign in to comment.