diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbb6bf162b8f9..b7bc01a121e45 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,10 @@
# Change Log
+
+## v1.12.0
+
+- [property-view] added initial version of a selection-based property-view [#8655](https://github.com/eclipse-theia/theia/pull/8655)
+ - A default implementation is available for file selections (via file navigator and default editors).
+
## v1.11.0 - 2/25/2021
- [api-samples] added example to echo the currently supported vscode API version [#8191](https://github.com/eclipse-theia/theia/pull/8191)
diff --git a/configs/root-compilation.tsconfig.json b/configs/root-compilation.tsconfig.json
index 9544d5b8eab68..a2524c2381a6b 100644
--- a/configs/root-compilation.tsconfig.json
+++ b/configs/root-compilation.tsconfig.json
@@ -138,6 +138,9 @@
},
{
"path": "../packages/timeline/compile.tsconfig.json"
+ },
+ {
+ "path": "../packages/property-view/compile.tsconfig.json"
}
]
}
diff --git a/examples/api-tests/src/menus.spec.js b/examples/api-tests/src/menus.spec.js
index 0e1721ea350c5..c84fd45248ae4 100644
--- a/examples/api-tests/src/menus.spec.js
+++ b/examples/api-tests/src/menus.spec.js
@@ -38,6 +38,7 @@ describe('Menus', function () {
const { OutputContribution } = require('@theia/output/lib/browser/output-contribution');
const { PluginFrontendViewContribution } = require('@theia/plugin-ext/lib/main/browser/plugin-frontend-view-contribution');
const { ProblemContribution } = require('@theia/markers/lib/browser/problem/problem-contribution');
+ const { PropertyViewContribution } = require('@theia/property-view/lib/browser/property-view-contribution');
const { SearchInWorkspaceFrontendContribution } = require('@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution');
const { HostedPluginSupport } = require('@theia/plugin-ext/lib/hosted/browser/hosted-plugin');
@@ -67,6 +68,7 @@ describe('Menus', function () {
container.get(OutputContribution),
container.get(PluginFrontendViewContribution),
container.get(ProblemContribution),
+ container.get(PropertyViewContribution),
container.get(SearchInWorkspaceFrontendContribution)
]) {
it(`should toggle '${contribution.viewLabel}' view`, async () => {
diff --git a/examples/api-tests/src/views.spec.js b/examples/api-tests/src/views.spec.js
index 7e494c0cc8be5..31c86e72da297 100644
--- a/examples/api-tests/src/views.spec.js
+++ b/examples/api-tests/src/views.spec.js
@@ -25,6 +25,7 @@ describe('Views', function () {
const { ScmContribution } = require('@theia/scm/lib/browser/scm-contribution');
const { OutlineViewContribution } = require('@theia/outline-view/lib/browser/outline-view-contribution');
const { ProblemContribution } = require('@theia/markers/lib/browser/problem/problem-contribution');
+ const { PropertyViewContribution } = require('@theia/property-view/lib/browser/property-view-contribution');
const { HostedPluginSupport } = require('@theia/plugin-ext/lib/hosted/browser/hosted-plugin');
/** @type {import('inversify').Container} */
@@ -34,6 +35,7 @@ describe('Views', function () {
const scmContribution = container.get(ScmContribution);
const outlineContribution = container.get(OutlineViewContribution);
const problemContribution = container.get(ProblemContribution);
+ const propertyViewContribution = container.get(PropertyViewContribution);
const pluginService = container.get(HostedPluginSupport);
before(() => Promise.all([
@@ -44,7 +46,7 @@ describe('Views', function () {
})()
]));
- for (const contribution of [navigatorContribution, scmContribution, outlineContribution, problemContribution]) {
+ for (const contribution of [navigatorContribution, scmContribution, outlineContribution, problemContribution, propertyViewContribution]) {
it(`should toggle ${contribution.viewLabel}`, async function () {
let view = await contribution.closeView();
if (view) {
diff --git a/examples/browser/package.json b/examples/browser/package.json
index 206495babf513..d103d429e17df 100644
--- a/examples/browser/package.json
+++ b/examples/browser/package.json
@@ -42,6 +42,7 @@
"@theia/preferences": "1.11.0",
"@theia/preview": "1.11.0",
"@theia/process": "1.11.0",
+ "@theia/property-view": "1.11.0",
"@theia/scm": "1.11.0",
"@theia/scm-extra": "1.11.0",
"@theia/search-in-workspace": "1.11.0",
diff --git a/examples/electron/compile.tsconfig.json b/examples/electron/compile.tsconfig.json
index 98e92c28ac586..a121e3fa9b92f 100644
--- a/examples/electron/compile.tsconfig.json
+++ b/examples/electron/compile.tsconfig.json
@@ -124,6 +124,9 @@
},
{
"path": "../../packages/timeline/compile.tsconfig.json"
+ },
+ {
+ "path": "../../packages/property-view/compile.tsconfig.json"
}
]
}
diff --git a/examples/electron/package.json b/examples/electron/package.json
index f1c7eec942a09..ccf6f307f1d65 100644
--- a/examples/electron/package.json
+++ b/examples/electron/package.json
@@ -42,6 +42,7 @@
"@theia/preferences": "1.11.0",
"@theia/preview": "1.11.0",
"@theia/process": "1.11.0",
+ "@theia/property-view": "1.11.0",
"@theia/scm": "1.11.0",
"@theia/scm-extra": "1.11.0",
"@theia/search-in-workspace": "1.11.0",
diff --git a/packages/property-view/.eslintrc.js b/packages/property-view/.eslintrc.js
new file mode 100644
index 0000000000000..be9cf1a1b3dff
--- /dev/null
+++ b/packages/property-view/.eslintrc.js
@@ -0,0 +1,10 @@
+/** @type {import('eslint').Linter.Config} */
+module.exports = {
+ extends: [
+ '../../configs/build.eslintrc.json'
+ ],
+ parserOptions: {
+ tsconfigRootDir: __dirname,
+ project: 'compile.tsconfig.json'
+ }
+};
diff --git a/packages/property-view/README.md b/packages/property-view/README.md
new file mode 100644
index 0000000000000..46c9c72431c48
--- /dev/null
+++ b/packages/property-view/README.md
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
ECLIPSE THEIA - PROPERTY-VIEW EXTENSION
+
+
+
+
+
+## Description
+
+The `@theia/property-view` extension contributes a generic, global property view based on Theia's global selection.
+
+The property view widget can be opened/toggled either via menu _View->Properties_ or via shortcut Shift+Alt+P . It is located in the bottom dock area by default.
+
+The following two default content widgets are implemented in this extension:
+- EmptyPropertyViewWidget: If no other widget can be provided, a simple message (_No properties available_) is shown.
+- ResourcePropertyViewWidget: Can handle `FileSelection`s and `Navigatable` selections (which provide their resource URI) and displays the general `FileStat` information (e.g. location, name, last modified) in a TreeWidget.
+
+To contribute a specific property view, it is necessary to implement a `PropertyViewDataService` which gathers the property data for a selection as well as a `PropertyViewWidgetProvider` which provides a suitable content widget to display the property data for a specific selection inside the property view widget.
+
+
+
+## Additional Information
+
+- [API documentation for `@theia/property-view`](https://eclipse-theia.github.io/theia/docs/next/modules/property_view.html)
+- [Theia - GitHub](https://github.com/eclipse-theia/theia)
+- [Theia - Website](https://theia-ide.org/)
+
+## 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)
+
+## Trademark
+"Theia" is a trademark of the Eclipse Foundation
+https://www.eclipse.org/theia
+
+## 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)
diff --git a/packages/property-view/compile.tsconfig.json b/packages/property-view/compile.tsconfig.json
new file mode 100644
index 0000000000000..d3e8f1dc24e40
--- /dev/null
+++ b/packages/property-view/compile.tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../configs/base.tsconfig",
+ "compilerOptions": {
+ "composite": true,
+ "rootDir": "src",
+ "outDir": "lib"
+ },
+ "include": [
+ "src"
+ ],
+ "references": [
+ {
+ "path": "../core/compile.tsconfig.json"
+ },
+ {
+ "path": "../filesystem/compile.tsconfig.json"
+ }
+ ]
+}
diff --git a/packages/property-view/package.json b/packages/property-view/package.json
new file mode 100644
index 0000000000000..85f7da578183b
--- /dev/null
+++ b/packages/property-view/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@theia/property-view",
+ "version": "1.11.0",
+ "description": "Theia - Property View Extension",
+ "dependencies": {
+ "@theia/core": "1.11.0",
+ "@theia/filesystem": "1.11.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "theiaExtensions": [
+ {
+ "frontend": "lib/browser/property-view-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": {
+ "lint": "theiaext lint",
+ "build": "theiaext build",
+ "watch": "theiaext watch",
+ "clean": "theiaext clean",
+ "test": "theiaext test"
+ },
+ "devDependencies": {
+ "@theia/ext-scripts": "1.11.0"
+ },
+ "nyc": {
+ "extends": "../../configs/nyc.json"
+ }
+}
diff --git a/packages/property-view/src/browser/empty-property-view-widget-provider.tsx b/packages/property-view/src/browser/empty-property-view-widget-provider.tsx
new file mode 100644
index 0000000000000..15f6bdc7f5da3
--- /dev/null
+++ b/packages/property-view/src/browser/empty-property-view-widget-provider.tsx
@@ -0,0 +1,78 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { ReactWidget } from '@theia/core/lib/browser';
+import { injectable } from 'inversify';
+import * as React from 'react';
+import { PropertyViewContentWidget } from './property-view-content-widget';
+import { DefaultPropertyViewWidgetProvider } from './property-view-widget-provider';
+
+class EmptyPropertyViewWidget extends ReactWidget implements PropertyViewContentWidget {
+
+ static readonly ID = 'theia-empty-property-view';
+ static readonly LABEL = 'No Properties';
+
+ constructor() {
+ super();
+ this.id = EmptyPropertyViewWidget.ID;
+ this.title.label = EmptyPropertyViewWidget.LABEL;
+ this.title.caption = EmptyPropertyViewWidget.LABEL;
+ this.title.closable = false;
+ this.node.tabIndex = 0;
+ }
+
+ updatePropertyViewContent(): void {
+ this.update();
+ }
+
+ protected render(): React.ReactNode {
+ return this.emptyComponent;
+ }
+
+ protected emptyComponent: JSX.Element = No properties available.
;
+
+}
+
+/**
+ * `DefaultPropertyViewWidgetProvider` is implemented to provide the PropertyViewEmptyWidget
+ * if the given selection is undefined or no other provider can handle the given selection.
+ */
+@injectable()
+export class EmptyPropertyViewWidgetProvider extends DefaultPropertyViewWidgetProvider {
+
+ static readonly ID = 'no-properties';
+ readonly id = EmptyPropertyViewWidgetProvider.ID;
+ readonly label = 'DefaultPropertyViewWidgetProvider';
+
+ private emptyWidget: EmptyPropertyViewWidget;
+
+ constructor() {
+ super();
+ this.emptyWidget = new EmptyPropertyViewWidget();
+ }
+
+ canHandle(selection: Object | undefined): number {
+ return selection === undefined ? 1 : 0;
+ }
+
+ provideWidget(selection: Object | undefined): Promise {
+ return Promise.resolve(this.emptyWidget);
+ }
+
+ updateContentWidget(selection: Object | undefined): void {
+ this.emptyWidget.updatePropertyViewContent();
+ }
+}
diff --git a/packages/property-view/src/browser/property-data-service.ts b/packages/property-view/src/browser/property-data-service.ts
new file mode 100644
index 0000000000000..6bc8d9bd5429d
--- /dev/null
+++ b/packages/property-view/src/browser/property-data-service.ts
@@ -0,0 +1,48 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 PropertyDataService = Symbol('PropertyDataService');
+/**
+ * `PropertyDataService` should be implemented to provide property data for the given selection.
+ */
+export interface PropertyDataService {
+
+ /**
+ * A unique id for this provider.
+ */
+ readonly id: string;
+ /**
+ * A human-readable name for this provider.
+ */
+ readonly label?: string;
+
+ /**
+ * Test whether this provider can provide property data for the given selection.
+ * Return a nonzero number if this provider can provide; otherwise it cannot.
+ * Never reject.
+ *
+ * A returned value indicating a priority of this provider.
+ */
+ canHandleSelection(selection: Object | undefined): number;
+
+ /**
+ * Provide property data for the given selection.
+ * Resolve to a property view content widget.
+ * Never reject if `canHandle` return a positive number; otherwise should reject.
+ */
+ providePropertyData(selection: Object | undefined): Promise;
+
+}
diff --git a/packages/property-view/src/browser/property-view-content-widget.ts b/packages/property-view/src/browser/property-view-content-widget.ts
new file mode 100644
index 0000000000000..33cdfe490573d
--- /dev/null
+++ b/packages/property-view/src/browser/property-view-content-widget.ts
@@ -0,0 +1,22 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { Widget } from '@theia/core/lib/browser/widgets/widget';
+import { PropertyDataService } from './property-data-service';
+
+export interface PropertyViewContentWidget extends Widget {
+ updatePropertyViewContent(propertyDataService?: PropertyDataService, selection?: Object): void;
+}
diff --git a/packages/property-view/src/browser/property-view-contribution.ts b/packages/property-view/src/browser/property-view-contribution.ts
new file mode 100644
index 0000000000000..a4c802fc095cd
--- /dev/null
+++ b/packages/property-view/src/browser/property-view-contribution.ts
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
+import { injectable } from 'inversify';
+import { PropertyViewWidget } from './property-view-widget';
+
+@injectable()
+export class PropertyViewContribution extends AbstractViewContribution {
+
+ constructor() {
+ super({
+ widgetId: PropertyViewWidget.ID,
+ widgetName: PropertyViewWidget.LABEL,
+ defaultWidgetOptions: {
+ area: 'bottom'
+ },
+ toggleCommandId: 'property-view:toggle',
+ toggleKeybinding: 'shift+alt+p'
+ });
+ }
+
+}
diff --git a/packages/property-view/src/browser/property-view-frontend-module.ts b/packages/property-view/src/browser/property-view-frontend-module.ts
new file mode 100644
index 0000000000000..5912e5d850ab4
--- /dev/null
+++ b/packages/property-view/src/browser/property-view-frontend-module.ts
@@ -0,0 +1,47 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { bindViewContribution, WidgetFactory } from '@theia/core/lib/browser';
+import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
+import { ContainerModule } from 'inversify';
+import { EmptyPropertyViewWidgetProvider } from './empty-property-view-widget-provider';
+import { PropertyDataService } from './property-data-service';
+import { PropertyViewContribution } from './property-view-contribution';
+import { PropertyViewService } from './property-view-service';
+import { PropertyViewWidget } from './property-view-widget';
+import { PropertyViewWidgetProvider } from './property-view-widget-provider';
+import { bindResourcePropertyView } from './resource-property-view';
+import '../../src/browser/style/property-view.css';
+
+export default new ContainerModule(bind => {
+ bind(PropertyViewService).toSelf().inSingletonScope();
+
+ bindContributionProvider(bind, PropertyDataService);
+ bindContributionProvider(bind, PropertyViewWidgetProvider);
+
+ bind(EmptyPropertyViewWidgetProvider).toSelf().inSingletonScope();
+ bind(PropertyViewWidgetProvider).to(EmptyPropertyViewWidgetProvider);
+
+ bind(PropertyViewWidget).toSelf();
+ bind(WidgetFactory).toDynamicValue(({ container }) => ({
+ id: PropertyViewWidget.ID,
+ createWidget: () => container.get(PropertyViewWidget)
+ })).inSingletonScope();
+
+ bindViewContribution(bind, PropertyViewContribution);
+
+ bindResourcePropertyView(bind);
+});
diff --git a/packages/property-view/src/browser/property-view-service.ts b/packages/property-view/src/browser/property-view-service.ts
new file mode 100644
index 0000000000000..6097f1e39a917
--- /dev/null
+++ b/packages/property-view/src/browser/property-view-service.ts
@@ -0,0 +1,61 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { ContributionProvider, Prioritizeable } from '@theia/core';
+import { inject, injectable, named, postConstruct } from 'inversify';
+import { EmptyPropertyViewWidgetProvider } from './empty-property-view-widget-provider';
+import { PropertyViewWidgetProvider } from './property-view-widget-provider';
+
+/**
+ * `PropertyViewService` provides an access to existing property view widget providers.
+ */
+@injectable()
+export class PropertyViewService {
+
+ @inject(ContributionProvider) @named(PropertyViewWidgetProvider)
+ private readonly contributions: ContributionProvider;
+
+ @inject(EmptyPropertyViewWidgetProvider)
+ private readonly emptyWidgetProvider: EmptyPropertyViewWidgetProvider;
+
+ private providers: PropertyViewWidgetProvider[] = [];
+
+ @postConstruct()
+ init(): void {
+ this.providers = this.providers.concat(this.contributions.getContributions());
+ }
+
+ /**
+ * Return a property view widget provider with the highest priority for the given selection.
+ * Never reject, return DefaultProvider ('No properties available') if no other matches.
+ */
+ async getProvider(selection: Object | undefined): Promise {
+ const provider = await this.prioritize(selection);
+ return provider ?? this.emptyWidgetProvider;
+ }
+
+ protected async prioritize(selection: Object | undefined): Promise {
+ const prioritized = await Prioritizeable.prioritizeAll(this.providers, async (provider: PropertyViewWidgetProvider) => {
+ try {
+ return await provider.canHandle(selection);
+ } catch {
+ return 0;
+ }
+ });
+ return prioritized.length !== 0 ? prioritized[0].value : undefined;
+ }
+
+}
diff --git a/packages/property-view/src/browser/property-view-widget-provider.ts b/packages/property-view/src/browser/property-view-widget-provider.ts
new file mode 100644
index 0000000000000..50fec1d7e4114
--- /dev/null
+++ b/packages/property-view/src/browser/property-view-widget-provider.ts
@@ -0,0 +1,107 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { ContributionProvider, MaybePromise, Prioritizeable } from '@theia/core';
+import { inject, injectable, named, postConstruct } from 'inversify';
+import { PropertyDataService } from './property-data-service';
+import { PropertyViewContentWidget } from './property-view-content-widget';
+
+export const PropertyViewWidgetProvider = Symbol('PropertyViewWidgetProvider');
+export interface PropertyViewWidgetProvider {
+ /**
+ * A unique id for this provider.
+ */
+ id: string;
+ /**
+ * A human-readable name for this provider.
+ */
+ label?: string;
+
+ /**
+ * Test whether this provider can provide a widget for the given selection.
+ * A returned value indicating a priority of this provider.
+ *
+ * @param selection the global selection object
+ * @returns a nonzero number if this provider can provide; otherwise it cannot; never reject
+ */
+ canHandle(selection: Object | undefined): MaybePromise;
+
+ /**
+ * Provide a widget for the given selection.
+ * Never reject if `canHandle` return a positive number; otherwise should reject.
+ *
+ * @param selection the global selection object
+ * @returns a resolved property view content widget.
+ */
+ provideWidget(selection: Object | undefined): Promise;
+
+ /**
+ * Update the widget with the given selection.
+ * Never reject if `canHandle` return a positive number; otherwise should reject.
+ *
+ * @param selection the global selection object
+ * @returns a resolved property view content widget.
+ */
+ updateContentWidget(selection: Object | undefined): void;
+
+}
+/**
+ * `DefaultPropertyViewWidgetProvider` should be extended to provide a new content property view widget for the given selection.
+ */
+@injectable()
+export abstract class DefaultPropertyViewWidgetProvider implements PropertyViewWidgetProvider {
+
+ @inject(ContributionProvider) @named(PropertyDataService)
+ protected readonly contributions: ContributionProvider;
+
+ protected propertyDataServices: PropertyDataService[] = [];
+
+ id = 'default';
+ label = 'DefaultPropertyViewWidgetProvider';
+
+ @postConstruct()
+ init(): void {
+ this.propertyDataServices = this.propertyDataServices.concat(this.contributions.getContributions());
+ }
+
+ canHandle(selection: Object | undefined): MaybePromise {
+ return 0;
+ }
+
+ provideWidget(selection: Object | undefined): Promise {
+ throw new Error('not implemented');
+ }
+
+ updateContentWidget(selection: Object | undefined): void {
+ // no-op
+ }
+
+ protected async getPropertyDataService(selection: Object | undefined): Promise {
+ const dataService = await this.prioritize(selection);
+ return dataService ?? this.propertyDataServices[0];
+ }
+
+ protected async prioritize(selection: Object | undefined): Promise {
+ const prioritized = await Prioritizeable.prioritizeAll(this.propertyDataServices, async (service: PropertyDataService) => {
+ try {
+ return service.canHandleSelection(selection);
+ } catch {
+ return 0;
+ }
+ });
+ return prioritized.length !== 0 ? prioritized[0].value : undefined;
+ }
+}
diff --git a/packages/property-view/src/browser/property-view-widget.tsx b/packages/property-view/src/browser/property-view-widget.tsx
new file mode 100644
index 0000000000000..3606fa6087242
--- /dev/null
+++ b/packages/property-view/src/browser/property-view-widget.tsx
@@ -0,0 +1,112 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { Message } from '@phosphor/messaging/lib';
+import { Disposable, SelectionService } from '@theia/core';
+import { BaseWidget, MessageLoop, Widget } from '@theia/core/lib/browser/widgets/widget';
+import { DisposableCollection } from '@theia/core/lib/common/disposable';
+import { inject, injectable, postConstruct } from 'inversify';
+import { PropertyViewContentWidget } from './property-view-content-widget';
+import { PropertyViewService } from './property-view-service';
+
+@injectable()
+export class PropertyViewWidget extends BaseWidget {
+
+ static readonly ID = 'property-view';
+ static readonly LABEL = 'Properties';
+
+ protected contentWidget: PropertyViewContentWidget;
+
+ protected toDisposeOnDetach = new DisposableCollection();
+
+ @inject(PropertyViewService) protected readonly propertyViewService: PropertyViewService;
+ @inject(SelectionService) protected readonly selectionService: SelectionService;
+
+ @postConstruct()
+ init(): void {
+ this.id = PropertyViewWidget.ID;
+ this.title.label = PropertyViewWidget.LABEL;
+ this.title.caption = PropertyViewWidget.LABEL;
+ this.title.iconClass = 'fa fa-table';
+ this.title.closable = true;
+
+ this.addClass('theia-property-view-widget');
+ this.node.tabIndex = 0;
+
+ let disposed = false;
+ this.toDispose.push(Disposable.create(() => disposed = true));
+ this.toDispose.push(this.selectionService.onSelectionChanged((selection: Object | undefined) => {
+ this.propertyViewService.getProvider(selection).then(provider => {
+ provider.provideWidget(selection).then(contentWidget => {
+ if (!disposed) {
+ this.replaceContentWidget(contentWidget);
+ provider.updateContentWidget(selection);
+ }
+ });
+ });
+ }));
+ }
+
+ protected initializeContentWidget(selection: Object | undefined): void {
+ this.propertyViewService.getProvider(selection).then(provider => {
+ provider.provideWidget(selection).then(contentWidget => {
+ this.attachContentWidget(contentWidget);
+ provider.updateContentWidget(selection);
+ });
+ });
+ }
+
+ protected replaceContentWidget(newContentWidget: PropertyViewContentWidget): void {
+ if (this.contentWidget.id !== newContentWidget.id) {
+ if (this.contentWidget) {
+ Widget.detach(this.contentWidget);
+ }
+ this.attachContentWidget(newContentWidget);
+ }
+ }
+
+ protected attachContentWidget(newContentWidget: PropertyViewContentWidget): void {
+ this.contentWidget = newContentWidget;
+ Widget.attach(this.contentWidget, this.node);
+ this.toDisposeOnDetach = new DisposableCollection();
+ this.toDisposeOnDetach.push(Disposable.create(() => {
+ if (this.contentWidget) {
+ Widget.detach(this.contentWidget);
+ }
+ }));
+ this.update();
+ }
+
+ protected onAfterAttach(msg: Message): void {
+ super.onAfterAttach(msg);
+ this.initializeContentWidget(this.selectionService.selection);
+ }
+
+ protected onActivateRequest(msg: Message): void {
+ super.onActivateRequest(msg);
+ this.node.focus();
+ if (this.contentWidget) {
+ this.contentWidget.activate();
+ }
+ }
+
+ protected onResize(msg: Widget.ResizeMessage): void {
+ super.onResize(msg);
+ if (this.contentWidget) {
+ MessageLoop.sendMessage(this.contentWidget, msg);
+ }
+ }
+}
diff --git a/packages/property-view/src/browser/resource-property-view/index.ts b/packages/property-view/src/browser/resource-property-view/index.ts
new file mode 100644
index 0000000000000..a7709e66f8fc2
--- /dev/null
+++ b/packages/property-view/src/browser/resource-property-view/index.ts
@@ -0,0 +1,17 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 './resource-property-view-tree-container';
diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-data-service.ts b/packages/property-view/src/browser/resource-property-view/resource-property-data-service.ts
new file mode 100644
index 0000000000000..b91759b2a85cf
--- /dev/null
+++ b/packages/property-view/src/browser/resource-property-view/resource-property-data-service.ts
@@ -0,0 +1,61 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { Navigatable } from '@theia/core/lib/browser';
+import URI from '@theia/core/lib/common/uri';
+import { FileSelection } from '@theia/filesystem/lib/browser/file-selection';
+import { FileService } from '@theia/filesystem/lib/browser/file-service';
+import { FileStat } from '@theia/filesystem/lib/common/files';
+import { inject, injectable } from 'inversify';
+import { PropertyDataService } from '../property-data-service';
+
+@injectable()
+export class ResourcePropertyDataService implements PropertyDataService {
+
+ readonly id = 'resources';
+ readonly label = 'ResourcePropertyDataService';
+
+ @inject(FileService) protected readonly fileService: FileService;
+
+ canHandleSelection(selection: Object | undefined): number {
+ return (this.isFileSelection(selection) || this.isNavigatableSelection(selection)) ? 1 : 0;
+ }
+
+ protected isFileSelection(selection: Object | undefined): boolean {
+ return !!selection && Array.isArray(selection) && FileSelection.is(selection[0]);
+ }
+
+ protected isNavigatableSelection(selection: Object | undefined): boolean {
+ return !!selection && Navigatable.is(selection);
+ }
+
+ protected async getFileStat(uri: URI): Promise {
+ return this.fileService.resolve(uri);
+ }
+
+ async providePropertyData(selection: Object | undefined): Promise {
+ if (this.isFileSelection(selection) && Array.isArray(selection)) {
+ return this.getFileStat(selection[0].fileStat.resource);
+ } else if (this.isNavigatableSelection(selection)) {
+ const navigatableUri = (selection as Navigatable).getResourceUri();
+ if (navigatableUri) {
+ return this.getFileStat(navigatableUri);
+ }
+ }
+ return undefined;
+ }
+
+}
diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-label-provider.ts b/packages/property-view/src/browser/resource-property-view/resource-property-view-label-provider.ts
new file mode 100644
index 0000000000000..12e7c6d01dae6
--- /dev/null
+++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-label-provider.ts
@@ -0,0 +1,49 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { LabelProvider, LabelProviderContribution, TreeNode } from '@theia/core/lib/browser';
+import { inject, injectable } from 'inversify';
+import { ResourcePropertiesCategoryNode, ResourcePropertiesItemNode } from './resource-property-view-tree-items';
+
+const DEFAULT_INFO_ICON = 'fa fa-info-circle';
+
+@injectable()
+export class ResourcePropertiesLabelProvider implements LabelProviderContribution {
+
+ @inject(LabelProvider) protected readonly labelProvider: LabelProvider;
+
+ canHandle(element: TreeNode): number {
+ return (ResourcePropertiesCategoryNode.is(element) || ResourcePropertiesItemNode.is(element)) ? 75 : 0;
+ }
+
+ getIcon(node: ResourcePropertiesCategoryNode | ResourcePropertiesItemNode): string {
+ if (ResourcePropertiesCategoryNode.is(node)) {
+ return node.icon ?? DEFAULT_INFO_ICON;
+ }
+ return node.icon ?? '';
+ }
+
+ getName(node: ResourcePropertiesCategoryNode | ResourcePropertiesItemNode): string {
+ return node.name;
+ }
+
+ getLongName(node: ResourcePropertiesCategoryNode | ResourcePropertiesItemNode): string {
+ if (ResourcePropertiesItemNode.is(node)) {
+ return node.property;
+ }
+ return this.getName(node);
+ }
+}
diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-container.ts b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-container.ts
new file mode 100644
index 0000000000000..b44c498ab0756
--- /dev/null
+++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-container.ts
@@ -0,0 +1,46 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { createTreeContainer, LabelProviderContribution, TreeProps, TreeWidget } from '@theia/core/lib/browser';
+import { interfaces } from 'inversify';
+import { PropertyDataService } from '../property-data-service';
+import { PropertyViewWidgetProvider } from '../property-view-widget-provider';
+import { ResourcePropertyDataService } from './resource-property-data-service';
+import { ResourcePropertiesLabelProvider } from './resource-property-view-label-provider';
+import { ResourcePropertyViewTreeWidget } from './resource-property-view-tree-widget';
+import { ResourcePropertyViewWidgetProvider } from './resource-property-view-widget-provider';
+
+const RESOURCE_PROPERTY_VIEW_TREE_PROPS = {
+ multiSelect: true,
+ search: true,
+} as TreeProps;
+
+function createResourcePropertyViewTreeWidget(parent: interfaces.Container): ResourcePropertyViewTreeWidget {
+ const child = createTreeContainer(parent, RESOURCE_PROPERTY_VIEW_TREE_PROPS);
+ child.unbind(TreeWidget);
+ child.bind(ResourcePropertyViewTreeWidget).toSelf().inSingletonScope();
+ return child.get(ResourcePropertyViewTreeWidget);
+}
+
+export function bindResourcePropertyView(bind: interfaces.Bind): void {
+ bind(LabelProviderContribution).to(ResourcePropertiesLabelProvider).inSingletonScope();
+ bind(PropertyDataService).to(ResourcePropertyDataService).inSingletonScope();
+ bind(PropertyViewWidgetProvider).to(ResourcePropertyViewWidgetProvider).inSingletonScope();
+
+ bind(ResourcePropertyViewTreeWidget).toDynamicValue(ctx =>
+ createResourcePropertyViewTreeWidget(ctx.container)
+ );
+}
diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-items.ts b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-items.ts
new file mode 100644
index 0000000000000..ca3d81b41aed9
--- /dev/null
+++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-items.ts
@@ -0,0 +1,53 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { CompositeTreeNode, ExpandableTreeNode, SelectableTreeNode, TreeNode } from '@theia/core/lib/browser';
+
+export const ROOT_ID = 'ResourcePropertiesTree';
+
+export interface ResourcePropertiesRoot extends CompositeTreeNode {
+ children: ResourcePropertiesCategoryNode[];
+}
+export namespace ResourcePropertiesRoot {
+ export function is(node: Object | undefined): node is ResourcePropertiesRoot {
+ return CompositeTreeNode.is(node) && node.id === ROOT_ID;
+ }
+}
+
+export interface ResourcePropertiesCategoryNode extends ExpandableTreeNode, SelectableTreeNode {
+ name: string;
+ icon?: string;
+ children: ResourcePropertiesItemNode[];
+ parent: ResourcePropertiesRoot;
+ categoryId: string;
+}
+export namespace ResourcePropertiesCategoryNode {
+ export function is(node: TreeNode | undefined): node is ResourcePropertiesCategoryNode {
+ return ExpandableTreeNode.is(node) && SelectableTreeNode.is(node) && 'categoryId' in node;
+ }
+}
+
+export interface ResourcePropertiesItemNode extends SelectableTreeNode {
+ name: string;
+ icon?: string;
+ parent: ResourcePropertiesCategoryNode;
+ property: string;
+}
+export namespace ResourcePropertiesItemNode {
+ export function is(node: TreeNode | undefined): node is ResourcePropertiesItemNode {
+ return !!node && SelectableTreeNode.is(node) && 'property' in node;
+ }
+}
diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-widget.tsx b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-widget.tsx
new file mode 100644
index 0000000000000..899dc666130bf
--- /dev/null
+++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-widget.tsx
@@ -0,0 +1,212 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 {
+ ContextMenuRenderer,
+ LabelProvider,
+ NodeProps,
+ TreeModel,
+ TreeNode,
+ TreeProps,
+ TreeWidget
+} from '@theia/core/lib/browser';
+import { FileStat } from '@theia/filesystem/lib/common/files';
+import { inject, injectable, postConstruct } from 'inversify';
+import * as React from 'react';
+import { PropertyDataService } from '../property-data-service';
+import { PropertyViewContentWidget } from '../property-view-content-widget';
+import {
+ ResourcePropertiesCategoryNode,
+ ResourcePropertiesItemNode,
+ ResourcePropertiesRoot,
+ ROOT_ID
+} from './resource-property-view-tree-items';
+
+@injectable()
+export class ResourcePropertyViewTreeWidget extends TreeWidget implements PropertyViewContentWidget {
+
+ static readonly ID = 'resource-properties-tree-widget';
+ static readonly LABEL = 'Resource Properties Tree';
+
+ protected propertiesTree: Map;
+ protected currentSelection: Object | undefined;
+
+ @inject(LabelProvider) protected readonly labelProvider: LabelProvider;
+
+ constructor(
+ @inject(TreeProps) readonly props: TreeProps,
+ @inject(TreeModel) model: TreeModel,
+ @inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer
+ ) {
+ super(props, model, contextMenuRenderer);
+
+ model.root = {
+ id: ROOT_ID,
+ name: ResourcePropertyViewTreeWidget.LABEL,
+ parent: undefined,
+ visible: false,
+ children: []
+ } as ResourcePropertiesRoot;
+
+ this.propertiesTree = new Map();
+ }
+
+ @postConstruct()
+ protected init(): void {
+ super.init();
+
+ this.id = ResourcePropertyViewTreeWidget.ID + '-treeContainer';
+ this.addClass('treeContainer');
+
+ this.fillPropertiesTree();
+ }
+
+ protected updateNeeded(selection: Object | undefined): boolean {
+ return this.currentSelection !== selection;
+ }
+
+ updatePropertyViewContent(propertyDataService?: PropertyDataService, selection?: Object | undefined): void {
+ if (this.updateNeeded(selection)) {
+ this.currentSelection = selection;
+ if (propertyDataService) {
+ propertyDataService.providePropertyData(selection).then((fileStatObject?: FileStat) => {
+ this.fillPropertiesTree(fileStatObject);
+ });
+ }
+ }
+ }
+
+ protected fillPropertiesTree(fileStatObject?: FileStat): void {
+ if (fileStatObject) {
+ this.propertiesTree.clear();
+ const infoNode = this.createCategoryNode('info', 'Info');
+ this.propertiesTree.set('info', infoNode);
+
+ infoNode.children.push(this.createResultLineNode('isDirectory', 'Directory', fileStatObject.isDirectory, infoNode));
+ infoNode.children.push(this.createResultLineNode('isFile', 'File', fileStatObject.isFile, infoNode));
+ infoNode.children.push(this.createResultLineNode('isSymbolicLink', 'Symbolic link', fileStatObject.isSymbolicLink, infoNode));
+ infoNode.children.push(this.createResultLineNode('location', 'Location', this.getLocationString(fileStatObject), infoNode));
+ infoNode.children.push(this.createResultLineNode('name', 'Name', this.getFileName(fileStatObject), infoNode));
+ infoNode.children.push(this.createResultLineNode('path', 'Path', this.getFilePath(fileStatObject), infoNode));
+ infoNode.children.push(this.createResultLineNode('lastModification', 'Last modified', this.getLastModificationString(fileStatObject), infoNode));
+ infoNode.children.push(this.createResultLineNode('created', 'Created', this.getCreationTimeString(fileStatObject), infoNode));
+ infoNode.children.push(this.createResultLineNode('size', 'Size', this.getSizeString(fileStatObject), infoNode));
+ this.refreshModelChildren();
+ }
+ }
+
+ protected getLocationString(fileStat: FileStat): string {
+ return fileStat.resource.path.toString();
+ }
+
+ protected getFileName(fileStat: FileStat): string {
+ return this.labelProvider.getName(fileStat.resource);
+ }
+
+ protected getFilePath(fileStat: FileStat): string {
+ return this.labelProvider.getLongName(fileStat.resource);
+ }
+
+ protected getLastModificationString(fileStat: FileStat): string {
+ return fileStat.mtime ? new Date(fileStat.mtime).toLocaleString() : '';
+ }
+
+ protected getCreationTimeString(fileStat: FileStat): string {
+ return fileStat.ctime ? new Date(fileStat.ctime).toLocaleString() : '';
+ }
+
+ protected getSizeString(fileStat: FileStat): string {
+ return fileStat.size ? fileStat.size + ' bytes' : '';
+ }
+
+ /*
+ * Creating TreeNodes
+ */
+
+ protected createCategoryNode(categoryId: string, name: string): ResourcePropertiesCategoryNode {
+ return {
+ id: categoryId,
+ parent: this.model.root as ResourcePropertiesRoot,
+ name,
+ children: [],
+ categoryId,
+ selected: false,
+ expanded: true
+ };
+ }
+
+ protected createResultLineNode(id: string, name: string, property: boolean | string | undefined, parent: ResourcePropertiesCategoryNode): ResourcePropertiesItemNode {
+ return {
+ id: `${parent.id}::${id}`,
+ parent,
+ name: name,
+ property: property !== undefined ? String(property) : '',
+ selected: false
+ };
+ }
+
+ /**
+ * Rendering
+ */
+
+ protected async refreshModelChildren(): Promise {
+ if (ResourcePropertiesRoot.is(this.model.root)) {
+ this.model.root.children = Array.from(this.propertiesTree.values());
+ this.model.refresh();
+ }
+ }
+
+ protected renderCaption(node: TreeNode, props: NodeProps): React.ReactNode {
+ if (ResourcePropertiesCategoryNode.is(node)) {
+ return this.renderExpandableNode(node);
+ } else if (ResourcePropertiesItemNode.is(node)) {
+ return this.renderItemNode(node);
+ }
+ return undefined;
+ }
+
+ protected renderExpandableNode(node: ResourcePropertiesCategoryNode): React.ReactNode {
+ return
+
+ {this.toNodeName(node)}
+ ;
+ }
+
+ protected renderItemNode(node: ResourcePropertiesItemNode): React.ReactNode {
+ return
+
+ {this.toNodeName(node)}
+ {this.toNodeDescription(node)}
+ ;
+ }
+
+ protected createNodeAttributes(node: TreeNode, props: NodeProps): React.Attributes & React.HTMLAttributes {
+ return {
+ ...super.createNodeAttributes(node, props),
+ title: this.getNodeTooltip(node)
+ };
+ }
+
+ protected getNodeTooltip(node: TreeNode): string | undefined {
+ if (ResourcePropertiesCategoryNode.is(node)) {
+ return this.labelProvider.getName(node);
+ } else if (ResourcePropertiesItemNode.is(node)) {
+ return `${this.labelProvider.getName(node)}: ${this.labelProvider.getLongName(node)}`;
+ }
+ return undefined;
+ }
+
+}
diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-widget-provider.ts b/packages/property-view/src/browser/resource-property-view/resource-property-view-widget-provider.ts
new file mode 100644
index 0000000000000..bfe6b2117959b
--- /dev/null
+++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-widget-provider.ts
@@ -0,0 +1,51 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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 { Navigatable } from '@theia/core/lib/browser';
+import { FileSelection } from '@theia/filesystem/lib/browser/file-selection';
+import { inject, injectable } from 'inversify';
+import { DefaultPropertyViewWidgetProvider } from '../property-view-widget-provider';
+import { ResourcePropertyViewTreeWidget } from './resource-property-view-tree-widget';
+
+@injectable()
+export class ResourcePropertyViewWidgetProvider extends DefaultPropertyViewWidgetProvider {
+
+ @inject(ResourcePropertyViewTreeWidget) protected treeWidget: ResourcePropertyViewTreeWidget;
+
+ readonly id = 'resources';
+ readonly label = 'ResourcePropertyViewWidgetProvider';
+
+ canHandle(selection: Object | undefined): number {
+ return (this.isFileSelection(selection) || this.isNavigatableSelection(selection)) ? 1 : 0;
+ }
+
+ protected isFileSelection(selection: Object | undefined): boolean {
+ return !!selection && Array.isArray(selection) && FileSelection.is(selection[0]);
+ }
+
+ protected isNavigatableSelection(selection: Object | undefined): boolean {
+ return !!selection && Navigatable.is(selection);
+ }
+
+ provideWidget(selection: Object | undefined): Promise {
+ return Promise.resolve(this.treeWidget);
+ }
+
+ updateContentWidget(selection: Object | undefined): void {
+ this.getPropertyDataService(selection).then(service => this.treeWidget.updatePropertyViewContent(service, selection));
+ }
+
+}
diff --git a/packages/property-view/src/browser/style/property-view.css b/packages/property-view/src/browser/style/property-view.css
new file mode 100644
index 0000000000000..9c007d83fc22e
--- /dev/null
+++ b/packages/property-view/src/browser/style/property-view.css
@@ -0,0 +1,51 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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
+ ********************************************************************************/
+
+:root {
+ --theia-property-view-widget-padding: 5px;
+ --theia-empty-property-view-widget-padding: 8px;
+ --theia-resource-tree-node-icon-margin: 0 3px;
+ --theia-resource-tree-node-icon-flex-basis: 1.5%;
+ --theia-resource-tree-node-name-flex-basis: 30%;
+ --theia-resource-tree-node-property-flex-basis: 70%;
+}
+
+.theia-property-view-widget {
+ padding: var(--theia-border-width);
+}
+
+#theia-empty-property-view .theia-widget-noInfo {
+ padding: var(--theia-empty-property-view-widget-padding);
+}
+
+.theia-property-view-widget .treeContainer {
+ height: 100%;
+}
+
+.theia-resource-tree-node-icon {
+ margin: var(--theia-resource-tree-node-icon-margin);
+ flex-basis: var(--theia-resource-tree-node-icon-flex-basis);
+ align-self: center;
+ text-align: center;
+}
+
+.theia-resource-tree-node-name {
+ flex-basis: var(--theia-resource-tree-node-name-flex-basis);
+}
+
+.theia-resource-tree-node-property {
+ flex-basis: var(--theia-resource-tree-node-property-flex-basis);
+}
diff --git a/packages/property-view/src/package.spec.ts b/packages/property-view/src/package.spec.ts
new file mode 100644
index 0000000000000..6b99fce8fae0f
--- /dev/null
+++ b/packages/property-view/src/package.spec.ts
@@ -0,0 +1,29 @@
+/********************************************************************************
+ * Copyright (C) 2020 EclipseSource 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
+ ********************************************************************************/
+
+/* note: this bogus test file is required so that
+ we are able to run mocha unit tests on this
+ package, without having any actual unit tests in it.
+ This way a coverage report will be generated,
+ showing 0% coverage, instead of no report.
+ This file can be removed once we have real unit
+ tests in place. */
+
+describe('property-view package', () => {
+
+ it('support code coverage statistics', () => true);
+
+});
diff --git a/tsconfig.json b/tsconfig.json
index 734ba59947ac6..a3b9decb887d7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -171,6 +171,9 @@
],
"@theia/timeline/*": [
"packages/timeline/*"
+ ],
+ "@theia/property-view/lib/*": [
+ "packages/property-view/src/*"
]
}
}