Property View

Many IDEs, such as the traditional Eclipse IDE, have the notion of a global, extensible property view, which shows additional information about the current selection within the IDE. Property views are heavily used in those IDEs for showing details of elements in e.g. diagram editors, complex tree editors, or the file explorer selection. Therefore, the main idea is to have a global, generic property view in the IDE, but allow specific implementations to extend the contents of the global property view with specific additional information for some type of selection.

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: Displays additional information about a file (e.g. location, name, last modified) that is selected in the file explorer or in the active monaco editor.

Contribute a Custom Property View

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.

Here is a short example of how to implement an additional property view, which displays the name and whether it is a file or directory in a simple React widget based on the selection from the file explorer (assuming there would be no ResourcePropertyViewWidget of course):

The FileInfoPropertyDataService gathers the file information and delivers a custom object:

custom-data-service.ts:

export interface FileInfoPropertyObject {
    name: string;
    isDirectory: boolean;
}

@injectable()
export class FileInfoPropertyDataService implements PropertyDataService {

    readonly id = 'fileinfo';
    readonly label = 'FileInfoPropertyDataService';

    @inject(LabelProvider) protected readonly labelProvider: LabelProvider;

    canHandleSelection(selection: Object | undefined): number {
        return this.isFileSelection(selection) ? 1 : 0;
    }

    private isFileSelection(selection: Object | undefined): boolean {
        return !!selection && Array.isArray(selection) && FileSelection.is(selection[0]);
    }

    async providePropertyData(selection: Object | undefined): Promise<FileInfoPropertyObject | undefined> {
        if (this.isFileSelection(selection) && Array.isArray(selection)) {
            return {
                name: this.labelProvider.getName(selection[0].fileStat.resource),
                isDirectory: (selection[0].fileStat as FileStat).isDirectory
            };
        }
        return Promise.reject();
    }
}

The FileInfoPropertyWidget is a simple ReactWidget and displays the selected node and whether it is a file or directory:

custom-content-widget.tsx:

export class FileInfoPropertyViewWidget extends ReactWidget implements PropertyViewContentWidget {

    static readonly ID = 'file-info-property-view';
    static readonly LABEL = 'File Information';

    protected currentFileInfo: FileInfoPropertyObject;

    constructor() {
        super();
        this.id = FileInfoPropertyViewWidget.ID;
        this.title.label = FileInfoPropertyViewWidget.LABEL;
        this.title.caption = FileInfoPropertyViewWidget.LABEL;
        this.title.closable = false;
        this.node.tabIndex = 0;
    }

    updatePropertyViewContent(propertyDataService?: PropertyDataService, selection?: Object | undefined): void {
        if (propertyDataService) {
            propertyDataService.providePropertyData(selection).then((fileInfo: FileInfoPropertyObject) => this.currentFileInfo = fileInfo);
        }
        this.update();
    }

    protected render(): React.ReactNode {
        return (<div>
            {`Selected node in explorer: ${this.currentFileInfo.name} ${this.currentFileInfo.isDirectory ? '(Directory)' : '(File)'}`
    }
        </div>);
    }
}

The FileInfoPropertyViewWidgetProvider is responsible to provide the correct PropertyViewContentWidget based on the selection:

custom-widget-provider.ts:

@injectable()
export class FileInfoPropertyViewWidgetProvider extends DefaultPropertyViewWidgetProvider {

    override readonly id = 'fileinfo';
    override readonly label = 'FileInfoPropertyViewWidgetProvider';

    private fileInfoWidget: FileInfoPropertyViewWidget;

    constructor() {
        super();
        this.fileInfoWidget = new FileInfoPropertyViewWidget();
    }

    override canHandle(selection: Object | undefined): number {
        return this.isFileSelection(selection) ? 1 : 0;
    }

    private isFileSelection(selection: Object | undefined): boolean {
        return !!selection && Array.isArray(selection) && FileSelection.is(selection[0]);
    }

    override provideWidget(selection: Object | undefined): Promise<FileInfoPropertyViewWidget> {
        return Promise.resolve(this.fileInfoWidget);
    }

    override updateContentWidget(selection: Object | undefined): void {
        this.getPropertyDataService(selection).then(service => this.fileInfoWidget.updatePropertyViewContent(service, selection));
    }
}

In the frontend module of the application the FileInfoPropertyDataService as well as the FileInfoPropertyViewWidgetProvider are registered as follows:

bind(PropertyDataService).to(FileInfoPropertyDataService).inSingletonScope();
bind(PropertyViewWidgetProvider).to(FileInfoPropertyViewWidgetProvider).inSingletonScope();

Following these few steps should give the reader an idea on how to implement an own property view, consisting of a specific PropertyViewWidgetProvider and PropertyViewDataService.

The resulting property view will be displayed like this:

Property View - custom widget
Go to previous Page : Message ServiceGo to next page : Events