Skip to content

Commit

Permalink
Allow label providers to notify that changes require a refresh
Browse files Browse the repository at this point in the history
Signed-off-by: Nigel Westbury <nigelipse@miegel.org>
  • Loading branch information
westbury committed Nov 21, 2019
1 parent 2c6df90 commit b65c3d9
Show file tree
Hide file tree
Showing 27 changed files with 451 additions and 21 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ cache:
- dev-packages/application-manager/node_modules
- dev-packages/application-package/node_modules
- dev-packages/electron/node_modules
- examples/api-samples/node_modules
- examples/browser/node_modules
- examples/electron/node_modules
- node_modules
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v0.13.0

- [core] Switched the frontend application's shutdown hook from `window.unload` to `window.beforeunload`. [#6530](https://github.com/eclipse-theia/theia/issues/6530)
- [core] label providers can now notify that element labels and icons may have changed and should be refreshed [#5884](https://github.com/theia-ide/theia/pull/5884)
- [scm] added handling when opening diff-editors to respect preference `workbench.list.openMode` [#6481](https://github.com/eclipse-theia/theia/pull/6481)

Breaking changes:
Expand Down
9 changes: 9 additions & 0 deletions examples/api-samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Theia - Examples - API Samples - Label Provider

This package contains examples of how to use Theia's internal API. The examples here can provide code that enables new features to be tested. The examples also show how to use certain API.

The examples here are all deactivated by default. Commands are provided that toggles on the example behavior. These commands are prefixed with "API Sample".

## License
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
10 changes: 10 additions & 0 deletions examples/api-samples/compile.tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
]
}
39 changes: 39 additions & 0 deletions examples/api-samples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"private": true,
"name": "@theia/api-samples",
"version": "0.12.0",
"description": "Theia - Example code to demonstrate Theia API",
"dependencies": {
"@theia/core": "^0.12.0"
},
"theiaExtensions": [
{
"frontend": "lib/browser/api-samples-frontend-module"
}
],
"keywords": [
"theia-extension"
],
"license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0",
"repository": {
"type": "git",
"url": "https://github.com/eclipse-theia/theia.git"
},
"bugs": {
"url": "https://github.com/eclipse-theia/theia/issues"
},
"homepage": "https://github.com/eclipse-theia/theia",
"files": [
"lib",
"src"
],
"scripts": {
"prepare": "yarn run clean && yarn run build",
"clean": "theiaext clean",
"build": "theiaext build",
"watch": "theiaext watch"
},
"devDependencies": {
"@theia/ext-scripts": "^0.12.0"
}
}
55 changes: 55 additions & 0 deletions examples/api-samples/src/browser/api-samples-contribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/********************************************************************************
* Copyright (C) 2019 Arm 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, inject } from 'inversify';
import { Command, CommandContribution, CommandRegistry, CommandHandler } from '@theia/core';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { SampleDynamicLabelProviderContribution } from './sample-dynamic-label-provider-contribution';

export namespace ExampleLabelProviderCommands {
const EXAMPLE_CATEGORY = 'Examples';
export const TOGGLE_SAMPLE: Command = {
id: 'example_label_provider.toggle',
category: EXAMPLE_CATEGORY,
label: 'Toggle Dynamically-Changing Labels'
};
}

@injectable()
export class ApiSamplesContribution implements FrontendApplicationContribution, CommandContribution {

@inject(SampleDynamicLabelProviderContribution)
protected readonly labelProviderContribution: SampleDynamicLabelProviderContribution;

initialize(): void { }

registerCommands(commands: CommandRegistry): void {
commands.registerCommand(ExampleLabelProviderCommands.TOGGLE_SAMPLE, new ExampleLabelProviderCommandHandler(this.labelProviderContribution));
}

}

export class ExampleLabelProviderCommandHandler implements CommandHandler {

constructor(private readonly labelProviderContribution: SampleDynamicLabelProviderContribution) {
}

// tslint:disable-next-line:no-any
execute(...args: any[]): any {
this.labelProviderContribution.toggle();
}

}
28 changes: 28 additions & 0 deletions examples/api-samples/src/browser/api-samples-frontend-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/********************************************************************************
* Copyright (C) 2019 Arm 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 { ContainerModule } from 'inversify';
import { CommandContribution } from '@theia/core';
import { LabelProviderContribution } from '@theia/core/lib/browser/label-provider';
import { ApiSamplesContribution } from './api-samples-contribution';
import { SampleDynamicLabelProviderContribution } from './sample-dynamic-label-provider-contribution';

export default new ContainerModule(bind => {
bind(CommandContribution).to(ApiSamplesContribution).inSingletonScope();

bind(SampleDynamicLabelProviderContribution).toSelf().inSingletonScope();
bind(LabelProviderContribution).toService(SampleDynamicLabelProviderContribution);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/********************************************************************************
* Copyright (C) 2019 Arm 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 { DefaultUriLabelProviderContribution, DidChangeLabelEvent, FILE_ICON } from '@theia/core/lib/browser/label-provider';
import URI from '@theia/core/lib/common/uri';
import { injectable } from 'inversify';
import { Emitter, Event } from '@theia/core';

@injectable()
export class SampleDynamicLabelProviderContribution extends DefaultUriLabelProviderContribution {

protected isActive: boolean = false;

constructor() {
super();
const outer = this;

setInterval(() => {
if (this.isActive) {
outer.x++;
outer.fireLabelsDidChange();
}
}, 1000);
}

canHandle(element: object): number {
if (element.toString().includes('test')) {
return 30;
}
return 0;
}

toggle(): void {
this.isActive = !this.isActive;
this.fireLabelsDidChange();
}

private fireLabelsDidChange(): void {
this.onDidChangeEmitter.fire({
affects: (element: URI) => element.toString().includes('test')
});
}

private getUri(element: URI): URI {
return new URI(element.toString());
}

async getIcon(element: URI): Promise<string> {
const uri = this.getUri(element);
const icon = super.getFileIcon(uri);
if (!icon) {
return FILE_ICON;
}
return icon;
}

protected readonly onDidChangeEmitter = new Emitter<DidChangeLabelEvent>();
private x: number = 0;

getName(element: URI): string {
const uri = this.getUri(element);
if (this.isActive && uri.toString().includes('test')) {
return super.getName(uri) + '-' + this.x.toString(10);
} else {
return super.getName(uri);
}
}

getLongName(element: URI): string {
const uri = this.getUri(element);
return super.getLongName(uri);
}

get onDidChange(): Event<DidChangeLabelEvent> {
return this.onDidChangeEmitter.event;
}

}
1 change: 1 addition & 0 deletions examples/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
}
},
"dependencies": {
"@theia/api-samples": "^0.12.0",
"@theia/callhierarchy": "^0.12.0",
"@theia/console": "^0.12.0",
"@theia/core": "^0.12.0",
Expand Down
1 change: 1 addition & 0 deletions examples/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
}
},
"dependencies": {
"@theia/api-samples": "^0.12.0",
"@theia/callhierarchy": "^0.12.0",
"@theia/console": "^0.12.0",
"@theia/core": "^0.12.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export class CallHierarchyTreeWidget extends TreeWidget {
this.toDispose.push(this.model.onOpenNode((node: TreeNode) => {
this.openEditor(node, false);
}));
this.toDispose.push(
this.labelProvider.onDidChange(() => this.update())
);
}

initializeModel(selection: Location | undefined, languageId: string | undefined): void {
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/browser/diff-uris.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,9 @@ export class DiffUriLabelProviderContribution implements LabelProviderContributi
getIcon(uri: URI): string {
return 'fa fa-columns';
}

getConstituentUris(uri: URI): URI[] {
return DiffUris.decode(uri);
}

}
74 changes: 71 additions & 3 deletions packages/core/src/browser/label-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as fileIcons from 'file-icons-js';
import URI from '../common/uri';
import { ContributionProvider } from '../common/contribution-provider';
import { Prioritizeable, MaybePromise } from '../common/types';
import { Event, Emitter } from '../common';

export const FOLDER_ICON = 'fa fa-folder';
export const FILE_ICON = 'fa fa-file';
Expand Down Expand Up @@ -48,6 +49,17 @@ export interface LabelProviderContribution {
*/
getLongName?(element: object): string;

/**
* Emit when something has changed that may result in this label provider returning a different
* value for one or more properties (name, icon etc).
*/
readonly onDidChange?: Event<DidChangeLabelEvent>;

readonly getConstituentUris?: (compositeElement: object) => URI[];
}

export interface DidChangeLabelEvent {
affects(element: object): boolean;
}

@injectable()
Expand Down Expand Up @@ -88,10 +100,67 @@ export class DefaultUriLabelProviderContribution implements LabelProviderContrib
@injectable()
export class LabelProvider {

protected readonly onDidChangeEmitter = new Emitter<DidChangeLabelEvent>();

constructor(
@inject(ContributionProvider) @named(LabelProviderContribution)
protected readonly contributionProvider: ContributionProvider<LabelProviderContribution>
) { }
) {
const contributions = this.contributionProvider.getContributions();
for (const contribution of contributions) {
if (contribution.onDidChange) {
contribution.onDidChange(event => {
const affects = (uri: URI) => this.affects(uri, event, contribution);
this.onDidChangeEmitter.fire({ affects });
});
}
}
}

/**
* When the given event occurs, determine if the given URI could in any
* way be affected.
*
* If the event directly indicates that it affects the URI then of course we
* return `true`. However there may be label provider contributions that delegate
* back to the label provider. These contributors do not, and should not, listen for
* label provider events because that would cause infinite recursion.
*
* @param uri
* @param event
*/
protected affects(element: object, event: DidChangeLabelEvent, originatingContribution: LabelProviderContribution): boolean {

const contribs = this.findContribution(element);
const possibleContribsWithDups = [
contribs.find(c => c.getIcon !== undefined),
contribs.find(c => c.getName !== undefined),
contribs.find(c => c.getLongName !== undefined),
];
const possibleContribsWithoutDups = [...new Set(possibleContribsWithDups)];
for (const possibleContrib of possibleContribsWithoutDups) {
if (possibleContrib) {
if (possibleContrib === originatingContribution) {
if (event.affects(element)) {
return true;
}
}
if (possibleContrib.getConstituentUris) {
const constituentUris: URI[] = possibleContrib.getConstituentUris(element);
for (const constituentUri of constituentUris) {
if (this.affects(constituentUri, event, originatingContribution)) {
return true;
}
}
}
}
}
return false;
}

get onDidChange(): Event<DidChangeLabelEvent> {
return this.onDidChangeEmitter.event;
}

async getIcon(element: object): Promise<string> {
const contribs = this.findContribution(element);
Expand All @@ -117,7 +186,7 @@ export class LabelProvider {
if (!contrib) {
return '';
}
return contrib!.getLongName!(element);
return contrib.getLongName!(element);
}

protected findContribution(element: object): LabelProviderContribution[] {
Expand All @@ -126,5 +195,4 @@ export class LabelProvider {
);
return prioritized.map(c => c.value);
}

}
Loading

0 comments on commit b65c3d9

Please sign in to comment.