提交 943b7823 编写于 作者: B Benjamin Pasero

paper cut - offer a menu to control visibility of status bar entries

上级 bd39f3d7
......@@ -11,7 +11,13 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export const IStatusbarService = createDecorator<IStatusbarService>('statusbarService');
export const enum StatusbarAlignment {
LEFT, RIGHT
LEFT,
RIGHT
}
export interface IStatusbarEntryCategory {
id: string;
label: string;
}
/**
......@@ -19,6 +25,11 @@ export const enum StatusbarAlignment {
*/
export interface IStatusbarEntry {
/**
* The category of the entry is needed to allow users to hide entries via settings.
*/
readonly category: IStatusbarEntryCategory;
/**
* The text to show for the entry. You can embed icons in the text by leveraging the syntax:
*
......
......@@ -3,12 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar';
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../common/extHost.protocol';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { dispose } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
export class MainThreadStatusBar implements MainThreadStatusBarShape {
......@@ -25,8 +26,18 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
this.entries.clear();
}
$setEntry(id: number, extensionId: ExtensionIdentifier, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void {
const entry = { text, tooltip, command, color, extensionId };
$setEntry(id: number, extension: IExtensionDescription, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void {
const entry: IStatusbarEntry = {
category: {
id: extension.identifier.value,
label: localize('extensionLabel', "{0} (Extension)", extension.displayName || extension.name)
},
text,
tooltip,
command,
color,
extensionId: extension.identifier
};
// Reset existing entry if alignment or priority changed
let existingEntry = this.entries.get(id);
......
......@@ -495,7 +495,7 @@ export interface MainThreadQuickOpenShape extends IDisposable {
}
export interface MainThreadStatusBarShape extends IDisposable {
$setEntry(id: number, extensionId: ExtensionIdentifier | undefined, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void;
$setEntry(id: number, extension: IExtensionDescription | undefined, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void;
$dispose(id: number): void;
}
......
......@@ -7,7 +7,7 @@ import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/
import { StatusBarAlignment as ExtHostStatusBarAlignment, Disposable, ThemeColor } from './extHostTypes';
import { StatusBarItem, StatusBarAlignment } from 'vscode';
import { MainContext, MainThreadStatusBarShape, IMainContext } from './extHost.protocol';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
export class ExtHostStatusBarEntry implements StatusBarItem {
private static ID_GEN = 0;
......@@ -26,14 +26,14 @@ export class ExtHostStatusBarEntry implements StatusBarItem {
private _timeoutHandle: any;
private _proxy: MainThreadStatusBarShape;
private _extensionId?: ExtensionIdentifier;
private _extension?: IExtensionDescription;
constructor(proxy: MainThreadStatusBarShape, extensionId: ExtensionIdentifier | undefined, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) {
constructor(proxy: MainThreadStatusBarShape, extension: IExtensionDescription | undefined, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) {
this._id = ExtHostStatusBarEntry.ID_GEN++;
this._proxy = proxy;
this._alignment = alignment;
this._priority = priority;
this._extensionId = extensionId;
this._extension = extension;
}
public get id(): number {
......@@ -107,7 +107,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem {
this._timeoutHandle = undefined;
// Set to status bar
this._proxy.$setEntry(this.id, this._extensionId, this.text, this.tooltip, this.command, this.color,
this._proxy.$setEntry(this.id, this._extension, this.text, this.tooltip, this.command, this.color,
this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT,
this._priority);
}, 0);
......@@ -167,8 +167,8 @@ export class ExtHostStatusBar {
this._statusMessage = new StatusBarMessage(this);
}
createStatusBarEntry(extensionId: ExtensionIdentifier | undefined, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem {
return new ExtHostStatusBarEntry(this._proxy, extensionId, alignment, priority);
createStatusBarEntry(extension: IExtensionDescription | undefined, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem {
return new ExtHostStatusBarEntry(this._proxy, extension, alignment, priority);
}
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): Disposable {
......
......@@ -466,7 +466,7 @@ export function createApiFactory(
return extHostDialogs.showSaveDialog(options);
},
createStatusBarItem(position?: vscode.StatusBarAlignment, priority?: number): vscode.StatusBarItem {
return extHostStatusBar.createStatusBarEntry(extension.identifier, <number>position, priority);
return extHostStatusBar.createStatusBarEntry(extension, <number>position, priority);
},
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): vscode.Disposable {
return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable);
......
......@@ -221,10 +221,20 @@ registerEditorContribution(OpenWorkspaceButtonContribution);
// Register Editor Status
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* towards the left of the right hand side */));
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(
EditorStatus,
{ id: 'status.editor', label: nls.localize('status.editor', "Editor Status") },
StatusbarAlignment.RIGHT,
100 /* towards the left of the right hand side */
));
// Register Zoom Status
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(ZoomStatusbarItem, StatusbarAlignment.RIGHT, 101 /* to the left of editor status (100) */));
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(
ZoomStatusbarItem,
{ id: 'status.imageZoom', label: nls.localize('status.imageZoom', "Image Zoom") },
StatusbarAlignment.RIGHT,
101 /* to the left of editor status (100) */)
);
// Register Status Actions
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
......
......@@ -58,6 +58,7 @@ export class NotificationsStatus extends Disposable {
private updateNotificationsCenterStatusItem(): void {
const statusProperties: IStatusbarEntry = {
category: { id: 'status.notifications', label: localize('status.notifications', "Notifictions") },
text: this.currentNotifications.size === 0 ? '$(bell)' : `$(bell) ${this.currentNotifications.size}`,
command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER,
tooltip: this.getTooltip(),
......@@ -137,7 +138,10 @@ export class NotificationsStatus extends Disposable {
// Create new
let statusMessageEntry: IStatusbarEntryAccessor;
let showHandle: any = setTimeout(() => {
statusMessageEntry = this.statusbarService.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
statusMessageEntry = this.statusbarService.addEntry({
category: { id: 'status.message', label: localize('status.message', "Status Message") },
text: message
}, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
showHandle = null;
}, showAfter);
......
......@@ -5,7 +5,7 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { IDisposable } from 'vs/base/common/lifecycle';
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { StatusbarAlignment, IStatusbarEntryCategory } from 'vs/platform/statusbar/common/statusbar';
import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
......@@ -14,11 +14,18 @@ export interface IStatusbarItem {
}
export class StatusbarItemDescriptor {
syncDescriptor: SyncDescriptor0<IStatusbarItem>;
alignment: StatusbarAlignment;
priority: number;
constructor(ctor: IConstructorSignature0<IStatusbarItem>, alignment?: StatusbarAlignment, priority?: number) {
readonly syncDescriptor: SyncDescriptor0<IStatusbarItem>;
readonly category: IStatusbarEntryCategory;
readonly alignment: StatusbarAlignment;
readonly priority: number;
constructor(
ctor: IConstructorSignature0<IStatusbarItem>,
category: IStatusbarEntryCategory,
alignment?: StatusbarAlignment,
priority?: number
) {
this.category = category;
this.syncDescriptor = createSyncDescriptor(ctor);
this.alignment = alignment || StatusbarAlignment.LEFT;
this.priority = priority || 0;
......@@ -26,21 +33,16 @@ export class StatusbarItemDescriptor {
}
export interface IStatusbarRegistry {
readonly items: StatusbarItemDescriptor[];
registerStatusbarItem(descriptor: StatusbarItemDescriptor): void;
items: StatusbarItemDescriptor[];
}
class StatusbarRegistry implements IStatusbarRegistry {
private _items: StatusbarItemDescriptor[];
constructor() {
this._items = [];
}
get items(): StatusbarItemDescriptor[] {
return this._items;
}
private readonly _items: StatusbarItemDescriptor[] = [];
get items(): StatusbarItemDescriptor[] { return this._items; }
registerStatusbarItem(descriptor: StatusbarItemDescriptor): void {
this._items.push(descriptor);
......
......@@ -15,7 +15,7 @@ import { Part } from 'vs/workbench/browser/part';
import { IStatusbarRegistry, Extensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarEntryCategory } from 'vs/platform/statusbar/common/statusbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action, IAction } from 'vs/base/common/actions';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService';
......@@ -24,23 +24,208 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { isThemeColor } from 'vs/editor/common/editorCommon';
import { Color } from 'vs/base/common/color';
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, clearNode, removeClass, EventType } from 'vs/base/browser/dom';
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, clearNode, removeClass, EventType, hide, show } from 'vs/base/browser/dom';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { coalesce } from 'vs/base/common/arrays';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { Event, Emitter } from 'vs/base/common/event';
import { values } from 'vs/base/common/map';
interface IPendingStatusbarEntry {
entry: IStatusbarEntry;
alignment: StatusbarAlignment;
priority: number;
accessor?: IStatusbarEntryAccessor;
}
interface IStatusbarViewModelItem {
category: IStatusbarEntryCategory;
alignment: StatusbarAlignment;
priority: number;
}
class StatusbarViewModel extends Disposable {
private static readonly HIDDEN_CATEGORIES_KEY = 'workbench.statusbar.hidden';
private readonly _items: IStatusbarViewModelItem[] = [];
get items(): IStatusbarViewModelItem[] { return this._items; }
private readonly _onDidCategoryVisibilityChange: Emitter<IStatusbarEntryCategory> = this._register(new Emitter());
get onDidCategoryVisibilityChange(): Event<IStatusbarEntryCategory> { return this._onDidCategoryVisibilityChange.event; }
private hiddenCategories: Set<string>;
constructor(private storageService: IStorageService) {
super();
this.restoreState();
this.registerListeners();
}
private restoreState(): void {
const hiddenCategoriesRaw = this.storageService.get(StatusbarViewModel.HIDDEN_CATEGORIES_KEY, StorageScope.GLOBAL);
if (hiddenCategoriesRaw) {
try {
const hiddenCategoriesArray: string[] = JSON.parse(hiddenCategoriesRaw);
this.hiddenCategories = new Set(hiddenCategoriesArray);
} catch (error) {
// ignore parsing errors
}
}
if (!this.hiddenCategories) {
this.hiddenCategories = new Set<string>();
}
}
private registerListeners(): void {
this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
}
private onDidStorageChange(event: IWorkspaceStorageChangeEvent): void {
if (event.key === StatusbarViewModel.HIDDEN_CATEGORIES_KEY && event.scope === StorageScope.GLOBAL) {
// Keep current hidden categories
const currentHiddenCategories = this.hiddenCategories;
// Load latest state of hidden categories
this.restoreState();
const changedCategories = new Set<string>();
// Check for each category that is now visible
currentHiddenCategories.forEach(category => {
if (!this.hiddenCategories.has(category)) {
changedCategories.add(category);
}
});
// Check for each category that is now hidden
this.hiddenCategories.forEach(category => {
if (!currentHiddenCategories.has(category)) {
changedCategories.add(category);
}
});
// Notify listeners that visibility for categories that changed
this._items.forEach(item => {
if (changedCategories.has(item.category.id)) {
this._onDidCategoryVisibilityChange.fire(item.category);
changedCategories.delete(item.category.id);
}
});
}
}
add(item: IStatusbarViewModelItem): void {
this._items.push(item);
this.sort();
}
remove(item: IStatusbarViewModelItem): void {
const index = this._items.indexOf(item);
if (index >= 0) {
this._items.splice(index, 1);
}
}
isHidden(category: IStatusbarEntryCategory): boolean {
return this.hiddenCategories.has(category.id);
}
hide(category: IStatusbarEntryCategory): void {
if (!this.hiddenCategories.has(category.id)) {
this.hiddenCategories.add(category.id);
this._onDidCategoryVisibilityChange.fire(category);
this.saveState();
}
}
show(category: IStatusbarEntryCategory): void {
if (this.hiddenCategories.has(category.id)) {
this.hiddenCategories.delete(category.id);
this._onDidCategoryVisibilityChange.fire(category);
this.saveState();
}
}
private saveState(): void {
if (this.hiddenCategories.size > 0) {
this.storageService.store(StatusbarViewModel.HIDDEN_CATEGORIES_KEY, JSON.stringify(values(this.hiddenCategories)), StorageScope.GLOBAL);
} else {
this.storageService.remove(StatusbarViewModel.HIDDEN_CATEGORIES_KEY, StorageScope.GLOBAL);
}
}
private sort(): void {
this._items.sort((itemA, itemB) => {
if (itemA.alignment === itemB.alignment) {
return itemB.priority - itemA.priority;
}
if (itemA.alignment === StatusbarAlignment.LEFT) {
return -1;
}
if (itemB.alignment === StatusbarAlignment.LEFT) {
return 1;
}
return 0;
});
}
}
class ToggleStatusCategoryVisibilityAction extends Action {
constructor(private category: IStatusbarEntryCategory, private model: StatusbarViewModel) {
super(category.id, category.label, undefined, true);
this.checked = !model.isHidden(category);
}
run(): Promise<any> {
if (this.model.isHidden(this.category)) {
this.model.show(this.category);
} else {
this.model.hide(this.category);
}
return Promise.resolve(true);
}
}
class HideStatusCategoryAction extends Action {
constructor(private category: IStatusbarEntryCategory, private model: StatusbarViewModel) {
super(category.id, nls.localize('hide', "Hide"), undefined, true);
}
interface PendingEntry { entry: IStatusbarEntry; alignment: StatusbarAlignment; priority: number; accessor?: IStatusbarEntryAccessor; }
run(): Promise<any> {
this.model.hide(this.category);
return Promise.resolve(true);
}
}
export class StatusbarPart extends Part implements IStatusbarService {
_serviceBrand: ServiceIdentifier<IStatusbarService>;
private static readonly PRIORITY_PROP = 'statusbar-entry-priority';
private static readonly ALIGNMENT_PROP = 'statusbar-entry-alignment';
private static readonly PRIORITY_PROP = 'statusbar-item-priority';
private static readonly ALIGNMENT_PROP = 'statusbar-item-alignment';
private static readonly CATEGORY_PROP = 'statusbar-item-category';
//#region IView
......@@ -53,9 +238,9 @@ export class StatusbarPart extends Part implements IStatusbarService {
private styleElement: HTMLStyleElement;
private pendingEntries: PendingEntry[] = [];
private pendingEntries: IPendingStatusbarEntry[] = [];
private hideStatusBarAction: ToggleStatusbarVisibilityAction;
private readonly viewModel: StatusbarViewModel;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
......@@ -67,13 +252,27 @@ export class StatusbarPart extends Part implements IStatusbarService {
) {
super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
this.hideStatusBarAction = this._register(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, nls.localize('hideStatusBar', "Hide Status Bar")));
this.viewModel = this._register(new StatusbarViewModel(storageService));
this.registerListeners();
}
private registerListeners(): void {
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
this._register(this.viewModel.onDidCategoryVisibilityChange(category => this.onDidCategoryVisibilityChange(category)));
}
private onDidCategoryVisibilityChange(category: IStatusbarEntryCategory): void {
const isHidden = this.viewModel.isHidden(category);
const items = this.getEntries(category);
items.forEach(item => {
if (isHidden) {
hide(item);
} else {
show(item);
}
});
}
addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IStatusbarEntryAccessor {
......@@ -81,33 +280,47 @@ export class StatusbarPart extends Part implements IStatusbarService {
// As long as we have not been created into a container yet, record all entries
// that are pending so that they can get created at a later point
if (!this.element) {
const pendingEntry: PendingEntry = {
entry, alignment, priority
};
this.pendingEntries.push(pendingEntry);
const accessor: IStatusbarEntryAccessor = {
update: (entry: IStatusbarEntry) => {
if (pendingEntry.accessor) {
pendingEntry.accessor.update(entry);
} else {
pendingEntry.entry = entry;
}
},
dispose: () => {
if (pendingEntry.accessor) {
pendingEntry.accessor.dispose();
} else {
this.pendingEntries = this.pendingEntries.filter(entry => entry !== pendingEntry);
}
}
};
return accessor;
return this.doAddPendingEntry(entry, alignment, priority);
}
// Otherwise add to view
return this.doAddEntry(entry, alignment, priority);
}
private doAddPendingEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor {
const pendingEntry: IPendingStatusbarEntry = { entry, alignment, priority };
this.pendingEntries.push(pendingEntry);
const accessor: IStatusbarEntryAccessor = {
update: (entry: IStatusbarEntry) => {
if (pendingEntry.accessor) {
pendingEntry.accessor.update(entry);
} else {
pendingEntry.entry = entry;
}
},
dispose: () => {
if (pendingEntry.accessor) {
pendingEntry.accessor.dispose();
} else {
this.pendingEntries = this.pendingEntries.filter(entry => entry !== pendingEntry);
}
}
};
return accessor;
}
private doAddEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor {
// Add to view model
const viewModelItem: IStatusbarViewModelItem = { category: entry.category, alignment, priority };
this.viewModel.add(viewModelItem);
// Render entry in status bar
const el = this.doCreateStatusItem(alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined]));
const item = this.instantiationService.createInstance(StatusBarEntryItem, el, entry);
const itemContainer = this.doCreateStatusItem(entry.category, alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined]));
const item = this.instantiationService.createInstance(StatusbarEntryItem, itemContainer, entry);
// Insert according to priority
const container = this.element;
......@@ -119,14 +332,14 @@ export class StatusbarPart extends Part implements IStatusbarService {
alignment === StatusbarAlignment.LEFT && nPriority < priority ||
alignment === StatusbarAlignment.RIGHT && nPriority > priority
) {
container.insertBefore(el, neighbour);
container.insertBefore(itemContainer, neighbour);
inserted = true;
break;
}
}
if (!inserted) {
container.appendChild(el);
container.appendChild(itemContainer);
}
return {
......@@ -134,30 +347,42 @@ export class StatusbarPart extends Part implements IStatusbarService {
// Update beak
if (entry.showBeak) {
addClass(el, 'has-beak');
addClass(itemContainer, 'has-beak');
} else {
removeClass(el, 'has-beak');
removeClass(itemContainer, 'has-beak');
}
// Update entry
item.update(entry);
},
dispose: () => {
el.remove();
this.viewModel.remove(viewModelItem);
itemContainer.remove();
dispose(item);
}
};
}
private getEntries(alignment: StatusbarAlignment): HTMLElement[] {
private getEntries(scope: StatusbarAlignment | IStatusbarEntryCategory): HTMLElement[] {
const entries: HTMLElement[] = [];
const container = this.element;
const children = container.children;
for (let i = 0; i < children.length; i++) {
const childElement = <HTMLElement>children.item(i);
if (Number(childElement.getAttribute(StatusbarPart.ALIGNMENT_PROP)) === alignment) {
entries.push(childElement);
// By alignment
if (typeof scope === 'number') {
if (Number(childElement.getAttribute(StatusbarPart.ALIGNMENT_PROP)) === scope) {
entries.push(childElement);
}
}
// By category
else {
if (childElement.getAttribute(StatusbarPart.CATEGORY_PROP) === scope.id) {
entries.push(childElement);
}
}
}
......@@ -170,31 +395,48 @@ export class StatusbarPart extends Part implements IStatusbarService {
// Context menu support
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
// Fill in initial items that were contributed from the registry
// Initial status bar entries
this.createInitialStatusEntries();
return this.element;
}
private createInitialStatusEntries(): void {
const registry = Registry.as<IStatusbarRegistry>(Extensions.Statusbar);
const descriptors = registry.items.slice().sort((a, b) => {
if (a.alignment === b.alignment) {
if (a.alignment === StatusbarAlignment.LEFT) {
return b.priority - a.priority;
} else {
return a.priority - b.priority;
const descriptors = registry.items.slice().sort((itemA, itemB) => {
if (itemA.alignment === itemB.alignment) {
if (itemA.alignment === StatusbarAlignment.LEFT) {
return itemB.priority - itemA.priority;
}
} else if (a.alignment === StatusbarAlignment.LEFT) {
return itemA.priority - itemB.priority;
}
if (itemA.alignment === StatusbarAlignment.LEFT) {
return 1;
} else if (a.alignment === StatusbarAlignment.RIGHT) {
}
if (itemA.alignment === StatusbarAlignment.RIGHT) {
return -1;
} else {
return 0;
}
return 0;
});
for (const descriptor of descriptors) {
const item = this.instantiationService.createInstance(descriptor.syncDescriptor);
const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority);
// Fill in initial items that were contributed from the registry
for (const { category, alignment, priority, syncDescriptor } of descriptors) {
// Add to view model
const viewModelItem: IStatusbarViewModelItem = { category, alignment, priority };
this.viewModel.add(viewModelItem);
this._register(item.render(el));
this.element.appendChild(el);
// Render
const item = this.instantiationService.createInstance(syncDescriptor);
const itemContainer = this.doCreateStatusItem(category, alignment, priority);
this._register(item.render(itemContainer));
this.element.appendChild(itemContainer);
}
// Fill in pending entries if any
......@@ -204,39 +446,57 @@ export class StatusbarPart extends Part implements IStatusbarService {
entry.accessor = this.addEntry(entry.entry, entry.alignment, entry.priority);
}
}
return this.element;
}
private showContextMenu(e: MouseEvent): void {
EventHelper.stop(e, true);
const event = new StandardMouseEvent(e);
let actions: IAction[] | undefined = undefined;
this.contextMenuService.showContextMenu({
getAnchor: () => { return { x: event.posx, y: event.posy }; },
getActions: () => this.getContextMenuActions()
getAnchor: () => ({ x: event.posx, y: event.posy }),
getActions: () => {
actions = this.getContextMenuActions(event);
return actions;
},
onHide: () => {
if (actions) {
dispose(actions);
}
}
});
}
private getContextMenuActions(): IAction[] {
const actions: IAction[] = [];
private getContextMenuActions(event: StandardMouseEvent): IAction[] {
const actions: Action[] = [];
// Figure out if mouse is over an entry
let categoryUnderMouse: IStatusbarEntryCategory | undefined = undefined;
for (let element: HTMLElement | null = event.target; element; element = element.parentElement) {
if (element.hasAttribute(StatusbarPart.CATEGORY_PROP)) {
categoryUnderMouse = { id: element.getAttribute(StatusbarPart.CATEGORY_PROP)!, label: element.title };
break;
}
}
if (categoryUnderMouse) {
actions.push(new HideStatusCategoryAction(categoryUnderMouse, this.viewModel));
actions.push(new Separator());
}
// TODO@Ben collect more context menu actions
// .map(({ id, name, activityAction }) => (<IAction>{
// id,
// label: name || id,
// checked: this.isPinned(id),
// run: () => {
// if (this.isPinned(id)) {
// this.unpin(id);
// } else {
// this.pin(id, true);
// }
// }
// }));
// actions.push(new Separator());
// Show an entry per known status item category
const handledCategories = new Set<string>();
this.viewModel.items.forEach(item => {
if (!handledCategories.has(item.category.id)) {
actions.push(new ToggleStatusCategoryVisibilityAction(item.category, this.viewModel));
}
});
actions.push(this.hideStatusBarAction);
// Provide an action to hide the status bar at last
actions.push(new Separator());
actions.push(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, nls.localize('hideStatusBar', "Hide Status Bar")));
return actions;
}
......@@ -265,23 +525,30 @@ export class StatusbarPart extends Part implements IStatusbarService {
this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
}
private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement {
const el = document.createElement('div');
addClass(el, 'statusbar-item');
private doCreateStatusItem(category: IStatusbarEntryCategory, alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement {
const itemContainer = document.createElement('div');
itemContainer.title = category.label;
addClass(itemContainer, 'statusbar-item');
if (extraClasses) {
addClasses(el, ...extraClasses);
addClasses(itemContainer, ...extraClasses);
}
if (alignment === StatusbarAlignment.RIGHT) {
addClass(el, 'right');
addClass(itemContainer, 'right');
} else {
addClass(el, 'left');
addClass(itemContainer, 'left');
}
el.setAttribute(StatusbarPart.PRIORITY_PROP, String(priority));
el.setAttribute(StatusbarPart.ALIGNMENT_PROP, String(alignment));
itemContainer.setAttribute(StatusbarPart.PRIORITY_PROP, String(priority));
itemContainer.setAttribute(StatusbarPart.ALIGNMENT_PROP, String(alignment));
itemContainer.setAttribute(StatusbarPart.CATEGORY_PROP, category.id);
return el;
if (this.viewModel.isHidden(category)) {
hide(itemContainer);
}
return itemContainer;
}
layout(width: number, height: number): void {
......@@ -295,27 +562,20 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
}
let manageExtensionAction: ManageExtensionAction;
class StatusBarEntryItem extends Disposable {
class StatusbarEntryItem extends Disposable {
private entryDisposables: IDisposable[] = [];
constructor(
private container: HTMLElement,
entry: IStatusbarEntry,
@ICommandService private readonly commandService: ICommandService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IEditorService private readonly editorService: IEditorService,
@IThemeService private readonly themeService: IThemeService
) {
super();
if (!manageExtensionAction) {
manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction);
}
this.render(entry);
}
......@@ -355,19 +615,6 @@ class StatusBarEntryItem extends Disposable {
addClass(this.container, 'has-background-color');
}
// Context Menu
if (entry.extensionId) {
this.entryDisposables.push((addDisposableListener(textContainer, 'contextmenu', e => {
EventHelper.stop(e, true);
this.contextMenuService.showContextMenu({
getAnchor: () => this.container,
getActionsContext: () => entry.extensionId!.value,
getActions: () => [manageExtensionAction]
});
})));
}
this.container.appendChild(textContainer);
}
......@@ -416,19 +663,6 @@ class StatusBarEntryItem extends Disposable {
}
}
class ManageExtensionAction extends Action {
constructor(
@ICommandService private readonly commandService: ICommandService
) {
super('statusbar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
}
run(extensionId: string): Promise<any> {
return this.commandService.executeCommand('_extensions.manage', extensionId);
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
if (statusBarItemHoverBackground) {
......
......@@ -65,6 +65,7 @@ export class DebugStatusContribution implements IWorkbenchContribution {
private get entry(): IStatusbarEntry {
return {
category: { id: 'status.debug', label: nls.localize('status.debug', "Debug Configuration") },
text: this.getText(),
tooltip: nls.localize('selectAndStartDebug', "Select and start debug configuration"),
command: 'workbench.action.debug.selectandstart'
......
......@@ -84,6 +84,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
if (visible) {
const indicator: IStatusbarEntry = {
category: { id: 'status.profiler', label: nls.localize('status.profiler', "Extension Profiler") },
text: nls.localize('profilingExtensionHost', "$(sync~spin) Profiling Extension Host"),
tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."),
command: 'workbench.action.extensionHostProfilder.stop'
......
......@@ -13,6 +13,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v
// Register Statusbar item
Registry.as<IStatusbarRegistry>(Extensions.Statusbar).registerStatusbarItem(new StatusbarItemDescriptor(
FeedbackStatusbarItem,
{ id: 'status.feedback', label: localize('status.feedback', "Send Feedback") },
StatusbarAlignment.RIGHT,
-100 /* towards the end of the right hand side */
));
......
......@@ -6,7 +6,7 @@
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { FeedbackDropdown, IFeedback, IFeedbackDelegate, FEEDBACK_VISIBLE_CONFIG } from 'vs/workbench/contrib/feedback/electron-browser/feedback';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import product from 'vs/platform/product/node/product';
import { Themable, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme';
......@@ -14,8 +14,6 @@ import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector }
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { clearNode, EventHelper, addClass, removeClass, addDisposableListener } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
class TwitterFeedbackService implements IFeedbackDelegate {
......@@ -54,13 +52,11 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem {
private dropdown: FeedbackDropdown | undefined;
private enabled: boolean;
private container: HTMLElement;
private hideAction: HideAction;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IThemeService themeService: IThemeService
) {
......@@ -68,8 +64,6 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem {
this.enabled = this.configurationService.getValue(FEEDBACK_VISIBLE_CONFIG);
this.hideAction = this._register(this.instantiationService.createInstance(HideAction));
this.registerListeners();
}
......@@ -95,16 +89,6 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem {
}
}, true));
// Offer context menu to hide status bar entry
this._register(addDisposableListener(this.container, 'contextmenu', e => {
EventHelper.stop(e, true);
this.contextMenuService.showContextMenu({
getAnchor: () => this.container,
getActions: () => [this.hideAction]
});
}));
return this.update();
}
......@@ -143,19 +127,6 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem {
}
}
class HideAction extends Action {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super('feedback.hide', localize('hide', "Hide"));
}
run(extensionId: string): Promise<any> {
return this.configurationService.updateValue(FEEDBACK_VISIBLE_CONFIG, false);
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
if (statusBarItemHoverBackground) {
......
......@@ -286,6 +286,7 @@ class MarkersStatusBarContributions extends Disposable implements IWorkbenchCont
private getMarkersItem(): IStatusbarEntry {
const markersStatistics = this.markerService.getStatistics();
return {
category: { id: 'status.problems', label: localize('status.problems', "Problems") },
text: this.getMarkersText(markersStatistics),
tooltip: this.getMarkersTooltip(markersStatistics),
command: 'workbench.actions.view.toggleProblems'
......
......@@ -138,6 +138,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc
private renderWindowIndicator(text: string, tooltip?: string, command?: string): void {
const properties: IStatusbarEntry = {
category: { id: 'status.host', label: nls.localize('status.host', "Remote Host") },
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command
};
if (this.windowIndicatorEntry) {
......
......@@ -190,6 +190,7 @@ export class StatusBarController implements IWorkbenchContribution {
const disposables = new DisposableStore();
for (const c of commands) {
disposables.add(this.statusbarService.addEntry({
category: { id: 'status.scm', label: localize('status.scm', "Source Control") },
text: c.title,
tooltip: `${label} - ${c.tooltip}`,
command: c.id,
......
......@@ -182,6 +182,7 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
}
} else {
const itemProps: IStatusbarEntry = {
category: { id: 'status.runningTasks', label: nls.localize('status.runningTasks', "Running Tasks") },
text: `$(tools) ${tasks.length}`,
tooltip: nls.localize('runningTasks', "Show Running Tasks"),
command: 'workbench.action.tasks.showTasks',
......
......@@ -129,6 +129,7 @@ export class ProgressService implements IProgressService {
}
this._globalStatusEntry = this._statusbarService.addEntry({
category: { id: 'status.progress', label: localize('status.progress', "Progress Message") },
text: `$(sync~spin) ${text}`,
tooltip: title
}, StatusbarAlignment.LEFT);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册