提交 beae0293 编写于 作者: B Benjamin Pasero

fix #29937

上级 162a4ec3
......@@ -67,13 +67,21 @@ export interface IStatusbarService {
_serviceBrand: any;
/**
* Adds an entry to the statusbar with the given alignment and priority. Use the returned IDisposable
* to remove the statusbar entry.
* Adds an entry to the statusbar with the given alignment and priority. Use the returned accessor
* to update or remove the statusbar entry.
*/
addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority?: number): IDisposable;
addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority?: number): IStatusbarEntryAccessor;
/**
* Prints something to the status bar area with optional auto dispose and delay.
*/
setStatusMessage(message: string, autoDisposeAfter?: number, delayBy?: number): IDisposable;
}
export interface IStatusbarEntryAccessor extends IDisposable {
/**
* Allows to update an existing status bar entry.
*/
update(properties: IStatusbarEntry): void;
}
\ No newline at end of file
......@@ -3,47 +3,55 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment, IStatusbarEntryAccessor } 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 { dispose } from 'vs/base/common/lifecycle';
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
export class MainThreadStatusBar implements MainThreadStatusBarShape {
private readonly _entries: { [id: number]: IDisposable };
private readonly entries: Map<number, { accessor: IStatusbarEntryAccessor, alignment: MainThreadStatusBarAlignment, priority: number }> = new Map();
constructor(
extHostContext: IExtHostContext,
@IStatusbarService private readonly _statusbarService: IStatusbarService
) {
this._entries = Object.create(null);
}
_extHostContext: IExtHostContext,
@IStatusbarService private readonly statusbarService: IStatusbarService
) { }
dispose(): void {
for (const key in this._entries) {
this._entries[key].dispose();
}
this.entries.forEach(entry => entry.accessor.dispose());
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 };
// Reset existing entry if alignment or priority changed
let existingEntry = this.entries.get(id);
if (existingEntry && (existingEntry.alignment !== alignment || existingEntry.priority !== priority)) {
dispose(existingEntry.accessor);
this.entries.delete(id);
existingEntry = undefined;
}
// Dispose any old
this.$dispose(id);
// Create new entry if not existing
if (!existingEntry) {
this.entries.set(id, { accessor: this.statusbarService.addEntry(entry, alignment, priority), alignment, priority });
}
// Add new
const entry = this._statusbarService.addEntry({ text, tooltip, command, color, extensionId }, alignment, priority);
this._entries[id] = entry;
// Otherwise update
else {
existingEntry.accessor.update(entry);
}
}
$dispose(id: number) {
const disposeable = this._entries[id];
if (disposeable) {
disposeable.dispose();
const entry = this.entries.get(id);
if (entry) {
dispose(entry.accessor);
this.entries.delete(id);
}
delete this._entries[id];
}
}
......@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem } from 'vs/workbench/common/notifications';
import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
import { Disposable } from 'vs/base/common/lifecycle';
import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
import { localize } from 'vs/nls';
export class NotificationsStatus extends Disposable {
private statusItem: IDisposable;
private statusItem: IStatusbarEntryAccessor;
private isNotificationsCenterVisible: boolean;
private _counter: Set<INotificationViewItem>;
......@@ -64,19 +64,18 @@ export class NotificationsStatus extends Disposable {
}
private updateNotificationsStatusItem(): void {
// Dispose old first
if (this.statusItem) {
this.statusItem.dispose();
}
// Create new
this.statusItem = this.statusbarService.addEntry({
const statusProperties: IStatusbarEntry = {
text: this.count === 0 ? '$(bell)' : `$(bell) ${this.count}`,
command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER,
tooltip: this.getTooltip(),
showBeak: this.isNotificationsCenterVisible
}, StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */);
};
if (!this.statusItem) {
this.statusItem = this.statusbarService.addEntry(statusProperties, StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */);
} else {
this.statusItem.update(statusProperties);
}
}
private getTooltip(): string {
......@@ -98,12 +97,4 @@ export class NotificationsStatus extends Disposable {
return localize('notifications', "{0} New Notifications", this.count);
}
dispose() {
super.dispose();
if (this.statusItem) {
this.statusItem.dispose();
}
}
}
\ No newline at end of file
......@@ -6,16 +6,16 @@
import 'vs/css!./media/statusbarpart';
import * as nls from 'vs/nls';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { dispose, IDisposable, toDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle';
import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { Registry } from 'vs/platform/registry/common/platform';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Part } from 'vs/workbench/browser/part';
import { IStatusbarRegistry, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
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 } from 'vs/platform/statusbar/common/statusbar';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action } from 'vs/base/common/actions';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService';
......@@ -24,11 +24,12 @@ 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 } from 'vs/base/browser/dom';
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, clearNode, removeClass } from 'vs/base/browser/dom';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService } 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';
export class StatusbarPart extends Part implements IStatusbarService {
......@@ -46,10 +47,10 @@ export class StatusbarPart extends Part implements IStatusbarService {
//#endregion
private statusMsgDispose: IDisposable;
private statusMessageDispose: IDisposable;
private styleElement: HTMLStyleElement;
private pendingEntries: { entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number, disposable: IDisposable }[] = [];
private pendingEntries: { entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number, accessor: IStatusbarEntryAccessor }[] = [];
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
......@@ -67,24 +68,30 @@ export class StatusbarPart extends Part implements IStatusbarService {
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
}
addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable {
addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IStatusbarEntryAccessor {
// 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 = { entry, alignment, priority, disposable: Disposable.None };
const pendingEntry = {
entry, alignment, priority, accessor: {
update: (entry: IStatusbarEntry) => {
pendingEntry.entry = entry;
},
dispose: () => {
this.pendingEntries = this.pendingEntries.filter(entry => entry !== pendingEntry);
dispose(pendingEntry.accessor);
}
}
};
this.pendingEntries.push(pendingEntry);
return toDisposable(() => {
this.pendingEntries = this.pendingEntries.filter(e => e !== pendingEntry);
pendingEntry.disposable.dispose();
});
return pendingEntry.accessor;
}
// Render entry in status bar
const el = this.doCreateStatusItem(alignment, priority, entry.showBeak ? 'has-beak' : undefined);
const item = this.instantiationService.createInstance(StatusBarEntryItem, entry);
const toDispose = item.render(el);
const el = this.doCreateStatusItem(alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined]));
const item = this.instantiationService.createInstance(StatusBarEntryItem, el, entry);
// Insert according to priority
const container = this.element;
......@@ -106,13 +113,24 @@ export class StatusbarPart extends Part implements IStatusbarService {
container.appendChild(el);
}
return toDisposable(() => {
el.remove();
return {
update: entry => {
// Update beak
if (entry.showBeak) {
addClass(el, 'has-beak');
} else {
removeClass(el, 'has-beak');
}
if (toDispose) {
toDispose.dispose();
// Update entry
item.update(entry);
},
dispose: () => {
el.remove();
dispose(item);
}
});
};
}
private getEntries(alignment: StatusbarAlignment): HTMLElement[] {
......@@ -164,7 +182,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
while (this.pendingEntries.length) {
const entry = this.pendingEntries.shift();
if (entry) {
entry.disposable = this.addEntry(entry.entry, entry.alignment, entry.priority);
entry.accessor = this.addEntry(entry.entry, entry.alignment, entry.priority);
}
}
......@@ -195,11 +213,11 @@ 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, extraClass?: string): HTMLElement {
private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement {
const el = document.createElement('div');
addClass(el, 'statusbar-item');
if (extraClass) {
addClass(el, extraClass);
if (extraClasses) {
addClasses(el, ...extraClasses);
}
if (alignment === StatusbarAlignment.RIGHT) {
......@@ -215,20 +233,20 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
setStatusMessage(message: string, autoDisposeAfter: number = -1, delayBy: number = 0): IDisposable {
if (this.statusMsgDispose) {
this.statusMsgDispose.dispose(); // dismiss any previous
}
// Dismiss any previous
dispose(this.statusMessageDispose);
// Create new
let statusDispose: IDisposable;
let statusMessageEntry: IStatusbarEntryAccessor;
let showHandle: any = setTimeout(() => {
statusDispose = this.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
statusMessageEntry = this.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
showHandle = null;
}, delayBy);
let hideHandle: any;
// Dispose function takes care of timeouts and actual entry
const dispose = {
const statusMessageDispose = {
dispose: () => {
if (showHandle) {
clearTimeout(showHandle);
......@@ -238,18 +256,18 @@ export class StatusbarPart extends Part implements IStatusbarService {
clearTimeout(hideHandle);
}
if (statusDispose) {
statusDispose.dispose();
if (statusMessageEntry) {
statusMessageEntry.dispose();
}
}
};
this.statusMsgDispose = dispose;
this.statusMessageDispose = statusMessageDispose;
if (typeof autoDisposeAfter === 'number' && autoDisposeAfter > 0) {
hideHandle = setTimeout(() => dispose.dispose(), autoDisposeAfter);
hideHandle = setTimeout(() => statusMessageDispose.dispose(), autoDisposeAfter);
}
return dispose;
return statusMessageDispose;
}
layout(width: number, height: number): void {
......@@ -264,10 +282,12 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
let manageExtensionAction: ManageExtensionAction;
class StatusBarEntryItem implements IStatusbarItem {
class StatusBarEntryItem extends Disposable {
private entryDisposables: IDisposable[] = [];
constructor(
private entry: IStatusbarEntry,
private container: HTMLElement,
entry: IStatusbarEntry,
@ICommandService private readonly commandService: ICommandService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@INotificationService private readonly notificationService: INotificationService,
......@@ -276,78 +296,80 @@ class StatusBarEntryItem implements IStatusbarItem {
@IEditorService private readonly editorService: IEditorService,
@IThemeService private readonly themeService: IThemeService
) {
this.entry = entry;
super();
if (!manageExtensionAction) {
manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction);
}
this.render(entry);
}
render(el: HTMLElement): IDisposable {
let toDispose: IDisposable[] = [];
addClass(el, 'statusbar-entry');
update(entry: IStatusbarEntry): void {
clearNode(this.container);
this.entryDisposables = dispose(this.entryDisposables);
this.render(entry);
}
private render(entry: IStatusbarEntry): void {
// Text Container
let textContainer: HTMLElement;
if (this.entry.command) {
if (entry.command) {
textContainer = document.createElement('a');
toDispose.push(addDisposableListener(textContainer, 'click', () => this.executeCommand(this.entry.command!, this.entry.arguments)));
this.entryDisposables.push((addDisposableListener(textContainer, 'click', () => this.executeCommand(entry.command!, entry.arguments))));
} else {
textContainer = document.createElement('span');
}
// Label
new OcticonLabel(textContainer).text = this.entry.text;
new OcticonLabel(textContainer).text = entry.text;
// Tooltip
if (this.entry.tooltip) {
textContainer.title = this.entry.tooltip;
if (entry.tooltip) {
textContainer.title = entry.tooltip;
}
// Color (only applies to text container)
toDispose.push(this.applyColor(textContainer, this.entry.color));
this.applyColor(textContainer, entry.color);
// Background Color (applies to parent element to fully fill container)
if (this.entry.backgroundColor) {
toDispose.push(this.applyColor(el, this.entry.backgroundColor, true));
addClass(el, 'has-background-color');
if (entry.backgroundColor) {
this.applyColor(this.container, entry.backgroundColor, true);
addClass(this.container, 'has-background-color');
}
// Context Menu
if (this.entry.extensionId) {
toDispose.push(addDisposableListener(textContainer, 'contextmenu', e => {
if (entry.extensionId) {
this.entryDisposables.push((addDisposableListener(textContainer, 'contextmenu', e => {
EventHelper.stop(e, true);
this.contextMenuService.showContextMenu({
getAnchor: () => el,
getActionsContext: () => this.entry.extensionId!.value,
getAnchor: () => this.container,
getActionsContext: () => entry.extensionId!.value,
getActions: () => [manageExtensionAction]
});
}));
})));
}
el.appendChild(textContainer);
return toDisposable(() => toDispose = dispose(toDispose));
this.container.appendChild(textContainer);
}
private applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): IDisposable {
const disposable: IDisposable[] = [];
private applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void {
if (color) {
if (isThemeColor(color)) {
const colorId = color.id;
color = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString();
disposable.push(this.themeService.onThemeChange(theme => {
this.entryDisposables.push(((this.themeService.onThemeChange(theme => {
const colorValue = (theme.getColor(colorId) || Color.transparent).toString();
isBackground ? container.style.backgroundColor = colorValue : container.style.color = colorValue;
}));
}))));
}
isBackground ? container.style.backgroundColor = color : container.style.color = color;
}
return combinedDisposable(disposable);
}
private executeCommand(id: string, args?: unknown[]) {
......@@ -368,6 +390,12 @@ class StatusBarEntryItem implements IStatusbarItem {
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: 'status bar' });
this.commandService.executeCommand(id, ...args).then(undefined, err => this.notificationService.error(toErrorMessage(err)));
}
dispose(): void {
super.dispose();
this.entryDisposables = dispose(this.entryDisposables);
}
}
class ManageExtensionAction extends Action {
......
......@@ -43,6 +43,7 @@
.task-statusbar-item-label {
display: inline-block;
cursor: pointer;
padding: 0 5px 0 5px;
}
.task-statusbar-item-label > .task-statusbar-item-label-counter {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册