Revert "Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d (#5949)" (#5983)

This reverts commit d15a3fcc98.
This commit is contained in:
Karl Burtram
2019-06-11 12:35:58 -07:00
committed by GitHub
parent 95a50b7892
commit 5a7562a37b
926 changed files with 11394 additions and 19540 deletions

View File

@@ -9,35 +9,20 @@
width: 100%;
height: 22px;
font-size: 12px;
display: flex;
}
.monaco-workbench .part.statusbar > .left-items,
.monaco-workbench .part.statusbar > .right-items {
display: flex;
flex-wrap: wrap; /* individual entries should not shrink since we do not control their content */
}
.monaco-workbench .part.statusbar > .right-items {
flex-direction: row-reverse; /* ensures that the most right elements wrap last when space is little */
}
.monaco-workbench .part.statusbar > .left-items {
flex-grow: 1; /* left items push right items to the far right end */
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item {
.monaco-workbench .part.statusbar > .statusbar-item {
display: inline-block;
line-height: 22px;
height: 100%;
vertical-align: top;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak {
.monaco-workbench .part.statusbar > .statusbar-item.has-beak {
position: relative;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before {
.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before {
content: '';
position: absolute;
left: 11px;
@@ -48,54 +33,66 @@
border-right: 5px solid transparent;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item > :first-child {
.monaco-workbench .part.statusbar > .statusbar-item.left > :first-child,
.monaco-workbench .part.statusbar > .statusbar-item.right > :first-child
{
margin-right: 3px;
margin-left: 3px;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item {
padding-left: 7px; /* Add padding to the most left status bar item */
.monaco-workbench .part.statusbar > .statusbar-item.right {
float: right;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item {
padding-right: 7px; /* Add padding to the most right status bar item */
/* adding padding to the most left status bar item */
.monaco-workbench .part.statusbar > .statusbar-item.left:first-child,
.monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.left {
padding-left: 7px;
}
/* adding padding to the most right status bar item */
.monaco-workbench .part.statusbar > .statusbar-item.right:first-child {
padding-right: 7px;
}
/* tweak appearance for items with background to improve hover feedback */
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.left.first-visible-item,
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.right.last-visible-item {
.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left:first-child,
.monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.has-background-color.left,
.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.right:first-child {
padding-right: 0;
padding-left: 0;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color > :first-child {
.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left > :first-child,
.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.right > :first-child
{
margin-right: 0;
margin-left: 0;
padding-left: 10px;
padding-right: 10px;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item a {
.monaco-workbench .part.statusbar > .statusbar-item a {
cursor: pointer;
display: inline-block;
height: 100%;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > span {
.monaco-workbench .part.statusbar > .statusbar-entry > span {
height: 100%;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > span,
.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > a {
.monaco-workbench .part.statusbar > .statusbar-entry > span,
.monaco-workbench .part.statusbar > .statusbar-entry > a {
padding: 0 5px 0 5px;
white-space: pre; /* gives some degree of styling */
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-entry span.octicon {
.monaco-workbench .part.statusbar > .statusbar-entry span.octicon {
text-align: center;
font-size: 14px;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover {
.monaco-workbench .part.statusbar > .statusbar-item a:hover {
text-decoration: none;
}

View File

@@ -14,21 +14,11 @@ export interface IStatusbarItem {
}
export class StatusbarItemDescriptor {
readonly syncDescriptor: SyncDescriptor0<IStatusbarItem>;
readonly id: string;
readonly name: string;
readonly alignment: StatusbarAlignment;
readonly priority: number;
syncDescriptor: SyncDescriptor0<IStatusbarItem>;
alignment: StatusbarAlignment;
priority: number;
constructor(
ctor: IConstructorSignature0<IStatusbarItem>,
id: string,
name: string,
alignment?: StatusbarAlignment,
priority?: number
) {
this.id = id;
this.name = name;
constructor(ctor: IConstructorSignature0<IStatusbarItem>, alignment?: StatusbarAlignment, priority?: number) {
this.syncDescriptor = createSyncDescriptor(ctor);
this.alignment = alignment || StatusbarAlignment.LEFT;
this.priority = priority || 0;
@@ -36,16 +26,21 @@ export class StatusbarItemDescriptor {
}
export interface IStatusbarRegistry {
readonly items: StatusbarItemDescriptor[];
registerStatusbarItem(descriptor: StatusbarItemDescriptor): void;
items: StatusbarItemDescriptor[];
}
class StatusbarRegistry implements IStatusbarRegistry {
private readonly _items: StatusbarItemDescriptor[] = [];
get items(): StatusbarItemDescriptor[] { return this._items; }
private _items: StatusbarItemDescriptor[];
constructor() {
this._items = [];
}
get items(): StatusbarItemDescriptor[] {
return this._items;
}
registerStatusbarItem(descriptor: StatusbarItemDescriptor): void {
this._items.push(descriptor);

View File

@@ -6,7 +6,7 @@
import 'vs/css!./media/statusbarpart';
import * as nls from 'vs/nls';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { dispose, IDisposable, Disposable, toDisposable } 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';
@@ -17,308 +17,28 @@ import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiat
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action, IAction } from 'vs/base/common/actions';
import { Action } from 'vs/base/common/actions';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/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, hide, show, removeClasses } 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, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
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';
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 { values } from 'vs/base/common/map';
interface IPendingStatusbarEntry {
id: string;
name: string;
entry: IStatusbarEntry;
alignment: StatusbarAlignment;
priority: number;
accessor?: IStatusbarEntryAccessor;
}
interface IStatusbarViewModelEntry {
id: string;
name: string;
alignment: StatusbarAlignment;
priority: number;
container: HTMLElement;
}
class StatusbarViewModel extends Disposable {
private static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden';
private readonly _entries: IStatusbarViewModelEntry[] = [];
get entries(): IStatusbarViewModelEntry[] { return this._entries; }
private hidden: Set<string>;
constructor(private storageService: IStorageService) {
super();
this.restoreState();
this.registerListeners();
}
private restoreState(): void {
const hiddenRaw = this.storageService.get(StatusbarViewModel.HIDDEN_ENTRIES_KEY, StorageScope.GLOBAL);
if (hiddenRaw) {
try {
const hiddenArray: string[] = JSON.parse(hiddenRaw);
this.hidden = new Set(hiddenArray);
} catch (error) {
// ignore parsing errors
}
}
if (!this.hidden) {
this.hidden = 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_ENTRIES_KEY && event.scope === StorageScope.GLOBAL) {
// Keep current hidden entries
const currentlyHidden = new Set(this.hidden);
// Load latest state of hidden entries
this.hidden.clear();
this.restoreState();
const changed = new Set<string>();
// Check for each entry that is now visible
currentlyHidden.forEach(id => {
if (!this.hidden.has(id)) {
changed.add(id);
}
});
// Check for each entry that is now hidden
this.hidden.forEach(id => {
if (!currentlyHidden.has(id)) {
changed.add(id);
}
});
// Update visibility for entries have changed
if (changed.size > 0) {
this._entries.forEach(entry => {
if (changed.has(entry.id)) {
this.updateVisibility(entry.id);
changed.delete(entry.id);
}
});
}
}
}
add(entry: IStatusbarViewModelEntry): IDisposable {
this._entries.push(entry); // intentionally not using a map here since multiple entries can have the same ID!
// Update visibility directly
this.updateVisibility(entry);
// Sort according to priority
this.sort();
// Mark first/last visible entry
this.markFirstLastVisibleEntry();
return toDisposable(() => this.remove(entry));
}
private remove(entry: IStatusbarViewModelEntry): void {
const index = this._entries.indexOf(entry);
if (index >= 0) {
this._entries.splice(index, 1);
// Mark first/last visible entry
this.markFirstLastVisibleEntry();
}
}
isHidden(id: string): boolean {
return this.hidden.has(id);
}
hide(id: string): void {
if (!this.hidden.has(id)) {
this.hidden.add(id);
this.updateVisibility(id);
this.saveState();
}
}
show(id: string): void {
if (this.hidden.has(id)) {
this.hidden.delete(id);
this.updateVisibility(id);
this.saveState();
}
}
findEntry(container: HTMLElement): IStatusbarViewModelEntry | undefined {
for (const entry of this._entries) {
if (entry.container === container) {
return entry;
}
}
return undefined;
}
getEntries(alignment: StatusbarAlignment): IStatusbarViewModelEntry[] {
return this._entries.filter(entry => entry.alignment === alignment);
}
private updateVisibility(id: string): void;
private updateVisibility(entry: IStatusbarViewModelEntry): void;
private updateVisibility(arg1: string | IStatusbarViewModelEntry): void {
// By identifier
if (typeof arg1 === 'string') {
const id = arg1;
for (const entry of this._entries) {
if (entry.id === id) {
this.updateVisibility(entry);
}
}
}
// By entry
else {
const entry = arg1;
const isHidden = this.isHidden(entry.id);
// Use CSS to show/hide item container
if (isHidden) {
hide(entry.container);
} else {
show(entry.container);
}
// Mark first/last visible entry
this.markFirstLastVisibleEntry();
}
}
private saveState(): void {
if (this.hidden.size > 0) {
this.storageService.store(StatusbarViewModel.HIDDEN_ENTRIES_KEY, JSON.stringify(values(this.hidden)), StorageScope.GLOBAL);
} else {
this.storageService.remove(StatusbarViewModel.HIDDEN_ENTRIES_KEY, StorageScope.GLOBAL);
}
}
private sort(): void {
this._entries.sort((entryA, entryB) => {
if (entryA.alignment === entryB.alignment) {
return entryB.priority - entryA.priority; // higher priority towards the left
}
if (entryA.alignment === StatusbarAlignment.LEFT) {
return -1;
}
if (entryB.alignment === StatusbarAlignment.LEFT) {
return 1;
}
return 0;
});
}
private markFirstLastVisibleEntry(): void {
this.doMarkFirstLastVisibleStatusbarItem(this.getEntries(StatusbarAlignment.LEFT));
this.doMarkFirstLastVisibleStatusbarItem(this.getEntries(StatusbarAlignment.RIGHT));
}
private doMarkFirstLastVisibleStatusbarItem(entries: IStatusbarViewModelEntry[]): void {
let firstVisibleItem: IStatusbarViewModelEntry | undefined;
let lastVisibleItem: IStatusbarViewModelEntry | undefined;
for (const entry of entries) {
// Clear previous first
removeClasses(entry.container, 'first-visible-item', 'last-visible-item');
const isVisible = !this.isHidden(entry.id);
if (isVisible) {
if (!firstVisibleItem) {
firstVisibleItem = entry;
}
lastVisibleItem = entry;
}
}
// Mark: first visible item
if (firstVisibleItem) {
addClass(firstVisibleItem.container, 'first-visible-item');
}
// Mark: last visible item
if (lastVisibleItem) {
addClass(lastVisibleItem.container, 'last-visible-item');
}
}
}
class ToggleStatusbarEntryVisibilityAction extends Action {
constructor(id: string, label: string, private model: StatusbarViewModel) {
super(id, label, undefined, true);
this.checked = !model.isHidden(id);
}
run(): Promise<any> {
if (this.model.isHidden(this.id)) {
this.model.show(this.id);
} else {
this.model.hide(this.id);
}
return Promise.resolve(true);
}
}
class HideStatusbarEntryAction extends Action {
constructor(id: string, private model: StatusbarViewModel) {
super(id, nls.localize('hide', "Hide"), undefined, true);
}
run(): Promise<any> {
this.model.hide(this.id);
return Promise.resolve(true);
}
}
interface PendingEntry { entry: IStatusbarEntry; alignment: StatusbarAlignment; priority: number; accessor?: IStatusbarEntryAccessor; }
export class StatusbarPart extends Part implements IStatusbarService {
_serviceBrand: ServiceIdentifier<IStatusbarService>;
_serviceBrand: ServiceIdentifier<any>;
private static readonly PRIORITY_PROP = 'statusbar-entry-priority';
private static readonly ALIGNMENT_PROP = 'statusbar-entry-alignment';
//#region IView
@@ -329,27 +49,20 @@ export class StatusbarPart extends Part implements IStatusbarService {
//#endregion
private statusMessageDispose: IDisposable;
private styleElement: HTMLStyleElement;
private pendingEntries: IPendingStatusbarEntry[] = [];
private readonly viewModel: StatusbarViewModel;
private leftItemsContainer: HTMLElement;
private rightItemsContainer: HTMLElement;
private pendingEntries: PendingEntry[] = [];
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IStorageService storageService: IStorageService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IContextMenuService private contextMenuService: IContextMenuService
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
) {
super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
this.viewModel = this._register(new StatusbarViewModel(storageService));
this.registerListeners();
}
@@ -357,233 +70,133 @@ export class StatusbarPart extends Part implements IStatusbarService {
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
}
addEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number = 0): IStatusbarEntryAccessor {
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) {
return this.doAddPendingEntry(entry, id, name, alignment, priority);
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;
}
// Otherwise add to view
return this.doAddEntry(entry, id, name, alignment, priority);
}
// 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);
private doAddPendingEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor {
const pendingEntry: IPendingStatusbarEntry = { entry, id, name, 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);
}
// Insert according to priority
const container = this.element;
const neighbours = this.getEntries(alignment);
let inserted = false;
for (const neighbour of neighbours) {
const nPriority = Number(neighbour.getAttribute(StatusbarPart.PRIORITY_PROP));
if (
alignment === StatusbarAlignment.LEFT && nPriority < priority ||
alignment === StatusbarAlignment.RIGHT && nPriority > priority
) {
container.insertBefore(el, neighbour);
inserted = true;
break;
}
};
}
return accessor;
}
private doAddEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor {
// Create item
const itemContainer = this.doCreateStatusItem(id, name, alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined]));
const item = this.instantiationService.createInstance(StatusbarEntryItem, itemContainer, entry);
// Append to parent
this.appendOneStatusbarEntry(itemContainer, alignment, priority);
// Add to view model
const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer };
const viewModelEntryDispose = this.viewModel.add(viewModelEntry);
if (!inserted) {
container.appendChild(el);
}
return {
update: entry => {
// Update beak
if (entry.showBeak) {
addClass(itemContainer, 'has-beak');
addClass(el, 'has-beak');
} else {
removeClass(itemContainer, 'has-beak');
removeClass(el, 'has-beak');
}
// Update entry
item.update(entry);
},
dispose: () => {
dispose(viewModelEntryDispose);
itemContainer.remove();
el.remove();
dispose(item);
}
};
}
updateEntryVisibility(id: string, visible: boolean): void {
if (visible) {
this.viewModel.show(id);
} else {
this.viewModel.hide(id);
private getEntries(alignment: StatusbarAlignment): 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);
}
}
return entries;
}
createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
// Left items container
this.leftItemsContainer = document.createElement('div');
addClasses(this.leftItemsContainer, 'left-items', 'items-container');
this.element.appendChild(this.leftItemsContainer);
// Right items container
this.rightItemsContainer = document.createElement('div');
addClasses(this.rightItemsContainer, 'right-items', 'items-container');
this.element.appendChild(this.rightItemsContainer);
// Context menu support
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
// Initial status bar entries
this.createInitialStatusbarEntries();
return this.element;
}
private createInitialStatusbarEntries(): void {
// Fill in initial items that were contributed from the registry
const registry = Registry.as<IStatusbarRegistry>(Extensions.Statusbar);
// Create initial items that were contributed from the registry
for (const { id, name, alignment, priority, syncDescriptor } of registry.items) {
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;
}
} else if (a.alignment === StatusbarAlignment.LEFT) {
return 1;
} else if (a.alignment === StatusbarAlignment.RIGHT) {
return -1;
} else {
return 0;
}
});
// Create item
const item = this.instantiationService.createInstance(syncDescriptor);
const itemContainer = this.doCreateStatusItem(id, name, alignment, priority);
this._register(item.render(itemContainer));
for (const descriptor of descriptors) {
const item = this.instantiationService.createInstance(descriptor.syncDescriptor);
const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority);
// Add to view model
const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer };
this.viewModel.add(viewModelEntry);
this._register(item.render(el));
this.element.appendChild(el);
}
// Add items in order according to alignment
this.appendAllStatusbarEntries();
// Fill in pending entries if any
while (this.pendingEntries.length) {
const pending = this.pendingEntries.shift();
if (pending) {
pending.accessor = this.addEntry(pending.entry, pending.id, pending.name, pending.alignment, pending.priority);
}
}
}
private appendAllStatusbarEntries(): void {
// Append in order of priority
[
...this.viewModel.getEntries(StatusbarAlignment.LEFT),
...this.viewModel.getEntries(StatusbarAlignment.RIGHT).reverse() // reversing due to flex: row-reverse
].forEach(entry => {
const target = entry.alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer;
target.appendChild(entry.container);
});
}
private appendOneStatusbarEntry(itemContainer: HTMLElement, alignment: StatusbarAlignment, priority: number): void {
const entries = this.viewModel.getEntries(alignment);
if (alignment === StatusbarAlignment.RIGHT) {
entries.reverse(); // reversing due to flex: row-reverse
}
const target = alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer;
// find an entry that has lower priority than the new one
// and then insert the item before that one
let appended = false;
for (const entry of entries) {
if (
alignment === StatusbarAlignment.LEFT && entry.priority < priority ||
alignment === StatusbarAlignment.RIGHT && entry.priority > priority // reversing due to flex: row-reverse
) {
target.insertBefore(itemContainer, entry.container);
appended = true;
break;
}
}
// Fallback to just appending otherwise
if (!appended) {
target.appendChild(itemContainer);
}
}
private showContextMenu(e: MouseEvent): void {
EventHelper.stop(e, true);
const event = new StandardMouseEvent(e);
let actions: IAction[] | undefined = undefined;
this.contextMenuService.showContextMenu({
getAnchor: () => ({ x: event.posx, y: event.posy }),
getActions: () => {
actions = this.getContextMenuActions(event);
return actions;
},
onHide: () => {
if (actions) {
dispose(actions);
}
}
});
}
private getContextMenuActions(event: StandardMouseEvent): IAction[] {
const actions: Action[] = [];
// Figure out if mouse is over an entry
let statusEntryUnderMouse: IStatusbarViewModelEntry | undefined = undefined;
for (let element: HTMLElement | null = event.target; element; element = element.parentElement) {
const entry = this.viewModel.findEntry(element);
const entry = this.pendingEntries.shift();
if (entry) {
statusEntryUnderMouse = entry;
break;
entry.accessor = this.addEntry(entry.entry, entry.alignment, entry.priority);
}
}
if (statusEntryUnderMouse) {
actions.push(new HideStatusbarEntryAction(statusEntryUnderMouse.id, this.viewModel));
actions.push(new Separator());
}
// Show an entry per known status entry
// Note: even though entries have an identifier, there can be multiple entries
// having the same identifier (e.g. from extensions). So we make sure to only
// show a single entry per identifier we handled.
const handledEntries = new Set<string>();
this.viewModel.entries.forEach(entry => {
if (!handledEntries.has(entry.id)) {
actions.push(new ToggleStatusbarEntryVisibilityAction(entry.id, entry.name, this.viewModel));
handledEntries.add(entry.id);
}
});
// 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;
return this.element;
}
updateStyles(): void {
@@ -607,25 +220,64 @@ export class StatusbarPart extends Part implements IStatusbarService {
this.styleElement = createStyleSheet(container);
}
this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
}
private doCreateStatusItem(id: string, name: string, alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement {
const itemContainer = document.createElement('div');
itemContainer.title = name;
addClass(itemContainer, 'statusbar-item');
private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement {
const el = document.createElement('div');
addClass(el, 'statusbar-item');
if (extraClasses) {
addClasses(itemContainer, ...extraClasses);
addClasses(el, ...extraClasses);
}
if (alignment === StatusbarAlignment.RIGHT) {
addClass(itemContainer, 'right');
addClass(el, 'right');
} else {
addClass(itemContainer, 'left');
addClass(el, 'left');
}
return itemContainer;
el.setAttribute(StatusbarPart.PRIORITY_PROP, String(priority));
el.setAttribute(StatusbarPart.ALIGNMENT_PROP, String(alignment));
return el;
}
setStatusMessage(message: string, autoDisposeAfter: number = -1, delayBy: number = 0): IDisposable {
// Dismiss any previous
dispose(this.statusMessageDispose);
// Create new
let statusMessageEntry: IStatusbarEntryAccessor;
let showHandle: any = setTimeout(() => {
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 statusMessageDispose = {
dispose: () => {
if (showHandle) {
clearTimeout(showHandle);
}
if (hideHandle) {
clearTimeout(hideHandle);
}
if (statusMessageEntry) {
statusMessageEntry.dispose();
}
}
};
this.statusMessageDispose = statusMessageDispose;
if (typeof autoDisposeAfter === 'number' && autoDisposeAfter > 0) {
hideHandle = setTimeout(() => statusMessageDispose.dispose(), autoDisposeAfter);
}
return statusMessageDispose;
}
layout(width: number, height: number): void {
@@ -639,20 +291,27 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
}
class StatusbarEntryItem extends Disposable {
let manageExtensionAction: ManageExtensionAction;
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);
}
@@ -692,6 +351,19 @@ 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);
}
@@ -740,30 +412,43 @@ 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) {
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`);
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`);
}
const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
if (statusBarItemActiveBackground) {
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:active { background-color: ${statusBarItemActiveBackground}; }`);
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a:active { background-color: ${statusBarItemActiveBackground}; }`);
}
const statusBarProminentItemForeground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_FOREGROUND);
if (statusBarProminentItemForeground) {
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item .status-bar-info { color: ${statusBarProminentItemForeground}; }`);
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item .status-bar-info { color: ${statusBarProminentItemForeground}; }`);
}
const statusBarProminentItemBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND);
if (statusBarProminentItemBackground) {
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`);
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`);
}
const statusBarProminentItemHoverBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND);
if (statusBarProminentItemHoverBackground) {
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a.status-bar-info:hover { background-color: ${statusBarProminentItemHoverBackground}; }`);
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a.status-bar-info:hover { background-color: ${statusBarProminentItemHoverBackground}; }`);
}
});