Skip to content

Commit

Permalink
CHE-9494 add the status bar API
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksii Orel <oorel@redhat.com>
  • Loading branch information
olexii4 authored and benoitf committed Jun 20, 2018
1 parent 389c045 commit 97ad39b
Show file tree
Hide file tree
Showing 11 changed files with 538 additions and 10 deletions.
14 changes: 13 additions & 1 deletion packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ export interface MessageRegistryMain {
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
items: string[] | theia.MessageItem[]): PromiseLike<string | theia.MessageItem | undefined>;
}

export interface StatusBarMessageRegistryMain {
$setMessage(text: string,
priority: number,
alignment: theia.StatusBarAlignment,
color: string | undefined,
tooltip: string | undefined,
command: string | undefined): PromiseLike<string>;
$dispose(id: string): void;
}

export interface QuickOpenExt {
$onItemSelected(handle: number): void;
$validateInput(input: string): PromiseLike<string> | undefined;
Expand All @@ -87,7 +98,8 @@ export interface WindowStateExt {
export const PLUGIN_RPC_CONTEXT = {
COMMAND_REGISTRY_MAIN: <ProxyIdentifier<CommandRegistryMain>>createProxyIdentifier<CommandRegistryMain>('CommandRegistryMain'),
QUICK_OPEN_MAIN: createProxyIdentifier<QuickOpenMain>('QuickOpenMain'),
MESSAGE_REGISTRY_MAIN: <ProxyIdentifier<MessageRegistryMain>>createProxyIdentifier<MessageRegistryMain>('MessageRegistryMain')
MESSAGE_REGISTRY_MAIN: <ProxyIdentifier<MessageRegistryMain>>createProxyIdentifier<MessageRegistryMain>('MessageRegistryMain'),
STATUS_BAR_MESSAGE_REGISTRY_MAIN: <ProxyIdentifier<StatusBarMessageRegistryMain>>createProxyIdentifier<StatusBarMessageRegistryMain>('StatusBarMessageRegistryMain')
};

export const MAIN_RPC_CONTEXT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class ModalNotification extends AbstractDialog<string | undefined> {
const iconContainer = messageNode.appendChild(document.createElement('div'));
iconContainer.classList.add(ICON);
const iconElement = iconContainer.appendChild(document.createElement('i'));
iconElement.classList.add('fa', this.toIconClass(messageType), 'fa-fw', messageType);
iconElement.classList.add('fa', this.toIconClass(messageType), 'fa-fw', messageType.toString());

const textContainer = messageNode.appendChild(document.createElement('div'));
textContainer.classList.add(TEXT);
Expand All @@ -69,7 +69,7 @@ export class ModalNotification extends AbstractDialog<string | undefined> {
return messageNode;
}

protected toIconClass(icon: string): string {
protected toIconClass(icon: MessageType): string {
if (icon === MessageType.Error) {
return 'fa-times-circle';
}
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/main/browser/main-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RPCProtocol } from '../../api/rpc-protocol';
import { PLUGIN_RPC_CONTEXT } from '../../api/plugin-api';
import { MessageRegistryMainImpl } from './message-registry-main';
import { WindowStateMain } from './window-state-main';
import {StatusBarMessageRegistryMainImpl} from './status-bar-message-registry-main';

export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void {
const commandRegistryMain = new CommandRegistryMainImpl(rpc, container);
Expand All @@ -25,4 +26,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container
// tslint:disable-next-line:no-unused-variable
// @ts-ignore
const windowStateMain = new WindowStateMain(rpc);

const statusBarMessageRegistryMain = new StatusBarMessageRegistryMainImpl(container);
rpc.set(PLUGIN_RPC_CONTEXT.STATUS_BAR_MESSAGE_REGISTRY_MAIN, statusBarMessageRegistryMain);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import {interfaces} from 'inversify';
import * as types from '../../plugin/types-impl';
import {StatusBarMessageRegistryMain} from '../../api/plugin-api';
import {StatusBar, StatusBarAlignment} from '@theia/core/lib/browser/status-bar/status-bar';

const STATUS_BAR_MESSAGE_PRE = 'status-bar-entry';

export class StatusBarMessageRegistryMainImpl implements StatusBarMessageRegistryMain {
private delegate: StatusBar;

private ids: string[] = [];

constructor(container: interfaces.Container) {
this.delegate = container.get(StatusBar);
}

$setMessage(text: string,
priority: number,
alignment: number,
color: string | undefined,
tooltip: string | undefined,
command: string | undefined): PromiseLike<string> {
const id = this.uniqueId;
this.ids.push(id);

const entry = {
text,
priority,
alignment: alignment === types.StatusBarAlignment.Left ? StatusBarAlignment.LEFT : StatusBarAlignment.RIGHT,
color,
tooltip,
command
};

return this.delegate.setElement(id, entry).then(() => Promise.resolve(id));
}

$dispose(id: string): void {
this.delegate.removeElement(id).then(() => {
const index = this.ids.indexOf(id);
if (index > -1) {
this.ids.splice(index, 1);
}
});
}

private get uniqueId(): string {
let extensionId = STATUS_BAR_MESSAGE_PRE;
for (let counter = 0; counter < 100; counter++) {
extensionId = `${STATUS_BAR_MESSAGE_PRE}_id_${('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4)}`;
if (this.ids.indexOf(extensionId) === -1) {
break;
}
}
return extensionId;
}
}
12 changes: 11 additions & 1 deletion packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ import { QuickOpenExtImpl } from './quick-open';
import { MAIN_RPC_CONTEXT, Plugin } from '../api/plugin-api';
import { RPCProtocol } from '../api/rpc-protocol';
import { getPluginId } from '../common/plugin-protocol';
import { Disposable } from './types-impl';
import { Disposable, StatusBarAlignment, ThemeColor } from './types-impl';
import { MessageRegistryExt } from './message-registry';
import { StatusBarMessageRegistryExt } from './status-bar-message-registry';
import { WindowStateExtImpl } from './window-state';

export function createAPI(rpc: RPCProtocol): typeof theia {
const commandRegistryExt = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc));
const quickOpenExt = rpc.set(MAIN_RPC_CONTEXT.QUICK_OPEN_EXT, new QuickOpenExtImpl(rpc));
const messageRegistryExt = new MessageRegistryExt(rpc);
const windowStateExt = rpc.set(MAIN_RPC_CONTEXT.WINDOW_STATE_EXT, new WindowStateExtImpl(rpc));
const statusBarMessageRegistryExt = new StatusBarMessageRegistryExt(rpc);

const commands: typeof theia.commands = {
registerCommand(command: theia.Command, handler?: <T>(...args: any[]) => T | Thenable<T>): Disposable {
Expand Down Expand Up @@ -56,6 +58,12 @@ export function createAPI(rpc: RPCProtocol): typeof theia {
...items: any[]): PromiseLike<any> {
return messageRegistryExt.showErrorMessage(message, optionsOrFirstItem, items);
},
setStatusBarMessage(text: string, arg?: number | PromiseLike<any>): Disposable {
return statusBarMessageRegistryExt.setStatusBarMessage(text, arg);
},
createStatusBarItem(alignment?: theia.StatusBarAlignment, priority?: number): theia.StatusBarItem {
return statusBarMessageRegistryExt.createStatusBarItem(alignment, priority);
},

get state(): theia.WindowState {
return windowStateExt.getWindowState();
Expand All @@ -69,6 +77,8 @@ export function createAPI(rpc: RPCProtocol): typeof theia {
commands,
window,
// Types
StatusBarAlignment: StatusBarAlignment,
ThemeColor: ThemeColor,
Disposable: Disposable,
EventEmitter: Emitter,
CancellationTokenSource: CancellationTokenSource
Expand Down
54 changes: 54 additions & 0 deletions packages/plugin-ext/src/plugin/status-bar-message-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import {Disposable, StatusBarAlignment} from './types-impl';
import {StatusBarItem} from '@theia/plugin';
import {
PLUGIN_RPC_CONTEXT as Ext, StatusBarMessageRegistryMain
} from '../api/plugin-api';
import {RPCProtocol} from '../api/rpc-protocol';
import {StatusBarItemImpl} from "./status-bar/status-bar-item";

export class StatusBarMessageRegistryExt {

proxy: StatusBarMessageRegistryMain;

constructor(rpc: RPCProtocol) {
this.proxy = rpc.getProxy(Ext.STATUS_BAR_MESSAGE_REGISTRY_MAIN);
}

setStatusBarMessage(text: string, arg?: number | PromiseLike<any>): Disposable {
let id: string;
this.proxy.$setMessage(text, 0, 1, undefined, undefined, undefined).then((messageId: string) => {
id = messageId;
});
let handle: NodeJS.Timer;

if (typeof arg === 'number') {
handle = setTimeout(() => this.dispose(id), <number>arg);
} else if (typeof arg !== 'undefined') {
arg.then(() => this.dispose(id), () => this.dispose(id));
}

return Disposable.create(() => {
this.dispose(id);
if (handle) {
clearTimeout(handle);
}
});
}

private dispose(id: string): void {
if (!id) {
return;
}
this.proxy.$dispose(id);
}

createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem {
return new StatusBarItemImpl(this.proxy, alignment, priority);
}
}
140 changes: 140 additions & 0 deletions packages/plugin-ext/src/plugin/status-bar/status-bar-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import {StatusBarItem} from '@theia/plugin';
import {ThemeColor, StatusBarAlignment} from '../types-impl';
import {StatusBarMessageRegistryMain} from "../../api/plugin-api";

export class StatusBarItemImpl implements StatusBarItem {
private _messageId: string;
private _alignment: StatusBarAlignment;
private _priority: number;

private _text: string;
private _tooltip: string;
private _color: string | ThemeColor;
private _command: string;

private _isVisible: boolean;
private _timeoutHandle: NodeJS.Timer | undefined;

_proxy: StatusBarMessageRegistryMain;

constructor(_proxy: StatusBarMessageRegistryMain,
alignment: StatusBarAlignment = StatusBarAlignment.Left,
priority: number = 0) {
this._proxy = _proxy;
this._alignment = alignment;
this._priority = priority;
}

public get alignment(): StatusBarAlignment {
return this._alignment;
}

public get priority(): number {
return this._priority;
}

public get text(): string {
return this._text;
}

public get tooltip(): string {
return this._tooltip;
}

public get color(): string | ThemeColor {
return this._color;
}

public get command(): string {
return this._command;
}

public set text(text: string) {
this._text = text;
this.update();
}

public set tooltip(tooltip: string) {
this._tooltip = tooltip;
this.update();
}

public set color(color: string | ThemeColor) {
this._color = color;
this.update();
}

public set command(command: string) {
this._command = command;
this.update();
}

public show(): void {
this._isVisible = true;
this.update();
}

public hide(): void {
if (this._timeoutHandle) {
clearTimeout(this._timeoutHandle);
}
if (this._messageId) {
this._proxy.$dispose(this._messageId);
}
this._isVisible = false;
}

private update(): void {
if (!this._isVisible) {
return;
}

if (this._messageId) {
this._proxy.$dispose(this._messageId);
}

if (this._timeoutHandle) {
clearTimeout(this._timeoutHandle);
}

// Defer the update so that multiple changes to setters don't cause a redraw each
this._timeoutHandle = setTimeout(() => {
this._timeoutHandle = undefined;

// Set to status bar
this._proxy.$setMessage(this.text,
this.priority,
this.alignment,
this.getColor(),
this.tooltip,
this.command).then((id: string) => {
this._messageId = id;
});
}, 0);
}

private getColor(): string | undefined {
if (typeof this.color !== 'string') {
const colorId = (<ThemeColor>this.color).id;
// TODO implement ThemeColor for vs (redo it)
const vsColorMap: Map<string, string> = new Map([
['statusBar.foreground', '--theia-layout-color4'],
['editorOverviewRuler.errorForeground', '--theia-error-color0'],
['editorOverviewRuler.warningForeground', '--theia-warn-color0'],
['editorOverviewRuler.infoForeground', '--theia-info-color0'],
]);
return `var(${vsColorMap.has(colorId) ? vsColorMap.get(colorId) : colorId})`;
}
return this.color;
}

public dispose(): void {
this.hide();
}
}
13 changes: 13 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,16 @@ export class Disposable {
return new Disposable(func);
}
}

export class ThemeColor {
id: string;

constructor(id: string) {
this.id = id;
}
}

export enum StatusBarAlignment {
Left = 1,
Right = 2
}
Loading

0 comments on commit 97ad39b

Please sign in to comment.