statusbarPart.ts 13.7 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

B
Benjamin Pasero 已提交
6
import 'vs/css!./media/statusbarpart';
7
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
8
import { toErrorMessage } from 'vs/base/common/errorMessage';
9
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
10
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
11
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
12
import { ICommandService } from 'vs/platform/commands/common/commands';
13
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Johannes Rieken 已提交
14
import { Part } from 'vs/workbench/browser/part';
15
import { IStatusbarRegistry, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
J
Johannes Rieken 已提交
16 17
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
18
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
19 20
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action } from 'vs/base/common/actions';
B
Benjamin Pasero 已提交
21
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
22
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_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';
23
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
24
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
25
import { isThemeColor } from 'vs/editor/common/editorCommon';
26
import { Color } from 'vs/base/common/color';
27
import { addClass, EventHelper, createStyleSheet, addDisposableListener, Dimension } from 'vs/base/browser/dom';
28
import { INotificationService } from 'vs/platform/notification/common/notification';
B
Benjamin Pasero 已提交
29
import { IStorageService } from 'vs/platform/storage/common/storage';
B
Benjamin Pasero 已提交
30
import { Event, Emitter } from 'vs/base/common/event';
31
import { ISerializableView } from 'vs/base/browser/ui/grid/grid';
32
import { Parts } from 'vs/workbench/services/layout/browser/layoutService';
E
Erich Gamma 已提交
33

34
export class StatusbarPart extends Part implements IStatusbarService, ISerializableView {
B
Benjamin Pasero 已提交
35
	_serviceBrand: any;
E
Erich Gamma 已提交
36

B
Benjamin Pasero 已提交
37 38
	private static readonly PRIORITY_PROP = 'statusbar-entry-priority';
	private static readonly ALIGNMENT_PROP = 'statusbar-entry-alignment';
E
Erich Gamma 已提交
39

40
	element: HTMLElement;
E
Erich Gamma 已提交
41

B
Benjamin Pasero 已提交
42 43 44 45
	readonly minimumWidth: number = 0;
	readonly maximumWidth: number = Number.POSITIVE_INFINITY;
	readonly minimumHeight: number = 22;
	readonly maximumHeight: number = 22;
46

B
Benjamin Pasero 已提交
47 48
	private _onDidChange = this._register(new Emitter<{ width: number; height: number; }>());
	get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.event; }
49

B
Benjamin Pasero 已提交
50
	private statusMsgDispose: IDisposable;
51 52
	private styleElement: HTMLStyleElement;

E
Erich Gamma 已提交
53
	constructor(
54
		id: string,
55
		@IInstantiationService private readonly instantiationService: IInstantiationService,
56
		@IThemeService themeService: IThemeService,
57
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
58
		@IStorageService storageService: IStorageService
E
Erich Gamma 已提交
59
	) {
60
		super(id, { hasTitle: false }, themeService, storageService);
E
Erich Gamma 已提交
61

B
Benjamin Pasero 已提交
62 63 64 65
		this.registerListeners();
	}

	private registerListeners(): void {
B
Benjamin Pasero 已提交
66
		this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
E
Erich Gamma 已提交
67 68
	}

B
Benjamin Pasero 已提交
69
	addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable {
E
Erich Gamma 已提交
70 71

		// Render entry in status bar
R
Rob Lourens 已提交
72
		const el = this.doCreateStatusItem(alignment, priority, entry.showBeak ? 'has-beak' : undefined);
B
Benjamin Pasero 已提交
73 74
		const item = this.instantiationService.createInstance(StatusBarEntryItem, entry);
		const toDispose = item.render(el);
E
Erich Gamma 已提交
75 76

		// Insert according to priority
77
		const container = this.element;
B
Benjamin Pasero 已提交
78
		const neighbours = this.getEntries(alignment);
E
Erich Gamma 已提交
79
		let inserted = false;
80
		for (const neighbour of neighbours) {
B
Benjamin Pasero 已提交
81
			const nPriority = Number(neighbour.getAttribute(StatusbarPart.PRIORITY_PROP));
E
Erich Gamma 已提交
82 83 84 85 86 87 88 89 90 91 92 93 94 95
			if (
				alignment === StatusbarAlignment.LEFT && nPriority < priority ||
				alignment === StatusbarAlignment.RIGHT && nPriority > priority
			) {
				container.insertBefore(el, neighbour);
				inserted = true;
				break;
			}
		}

		if (!inserted) {
			container.appendChild(el);
		}

96
		return toDisposable(() => {
97
			el.remove();
E
Erich Gamma 已提交
98

99 100
			if (toDispose) {
				toDispose.dispose();
E
Erich Gamma 已提交
101
			}
102
		});
E
Erich Gamma 已提交
103 104 105
	}

	private getEntries(alignment: StatusbarAlignment): HTMLElement[] {
B
Benjamin Pasero 已提交
106
		const entries: HTMLElement[] = [];
E
Erich Gamma 已提交
107

108
		const container = this.element;
B
Benjamin Pasero 已提交
109
		const children = container.children;
E
Erich Gamma 已提交
110
		for (let i = 0; i < children.length; i++) {
B
Benjamin Pasero 已提交
111
			const childElement = <HTMLElement>children.item(i);
B
Benjamin Pasero 已提交
112
			if (Number(childElement.getAttribute(StatusbarPart.ALIGNMENT_PROP)) === alignment) {
E
Erich Gamma 已提交
113 114 115 116 117 118 119
				entries.push(childElement);
			}
		}

		return entries;
	}

B
Benjamin Pasero 已提交
120
	createContentArea(parent: HTMLElement): HTMLElement {
121
		this.element = parent;
E
Erich Gamma 已提交
122 123

		// Fill in initial items that were contributed from the registry
B
Benjamin Pasero 已提交
124
		const registry = Registry.as<IStatusbarRegistry>(Extensions.Statusbar);
E
Erich Gamma 已提交
125

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
		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;
			}
		});
E
Erich Gamma 已提交
141

142
		for (const descriptor of descriptors) {
B
Benjamin Pasero 已提交
143 144
			const item = this.instantiationService.createInstance(descriptor.syncDescriptor);
			const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority);
E
Erich Gamma 已提交
145

B
Benjamin Pasero 已提交
146
			this._register(item.render(el));
147
			this.element.appendChild(el);
148
		}
E
Erich Gamma 已提交
149

150
		return this.element;
E
Erich Gamma 已提交
151 152
	}

153
	protected updateStyles(): void {
154 155
		super.updateStyles();

B
Benjamin Pasero 已提交
156
		const container = this.getContainer();
157

158 159
		// Background colors
		const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND);
B
Benjamin Pasero 已提交
160 161
		container.style.backgroundColor = backgroundColor;
		container.style.color = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND);
B
Benjamin Pasero 已提交
162

163
		// Border color
164
		const borderColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BORDER : STATUS_BAR_NO_FOLDER_BORDER) || this.getColor(contrastBorder);
B
Benjamin Pasero 已提交
165 166 167
		container.style.borderTopWidth = borderColor ? '1px' : null;
		container.style.borderTopStyle = borderColor ? 'solid' : null;
		container.style.borderTopColor = borderColor;
168 169 170

		// Notification Beak
		if (!this.styleElement) {
B
Benjamin Pasero 已提交
171
			this.styleElement = createStyleSheet(container);
172 173
		}

174
		this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
175 176
	}

177
	private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0, extraClass?: string): HTMLElement {
B
Benjamin Pasero 已提交
178
		const el = document.createElement('div');
179
		addClass(el, 'statusbar-item');
180 181 182
		if (extraClass) {
			addClass(el, extraClass);
		}
E
Erich Gamma 已提交
183 184

		if (alignment === StatusbarAlignment.RIGHT) {
185
			addClass(el, 'right');
E
Erich Gamma 已提交
186
		} else {
187
			addClass(el, 'left');
E
Erich Gamma 已提交
188 189
		}

B
Benjamin Pasero 已提交
190 191
		el.setAttribute(StatusbarPart.PRIORITY_PROP, String(priority));
		el.setAttribute(StatusbarPart.ALIGNMENT_PROP, String(alignment));
E
Erich Gamma 已提交
192 193 194 195

		return el;
	}

B
Benjamin Pasero 已提交
196
	setStatusMessage(message: string, autoDisposeAfter: number = -1, delayBy: number = 0): IDisposable {
197 198 199 200 201 202
		if (this.statusMsgDispose) {
			this.statusMsgDispose.dispose(); // dismiss any previous
		}

		// Create new
		let statusDispose: IDisposable;
M
Matt Bierner 已提交
203
		let showHandle: any = setTimeout(() => {
204
			statusDispose = this.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
205 206
			showHandle = null;
		}, delayBy);
207
		let hideHandle: any;
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

		// Dispose function takes care of timeouts and actual entry
		const dispose = {
			dispose: () => {
				if (showHandle) {
					clearTimeout(showHandle);
				}

				if (hideHandle) {
					clearTimeout(hideHandle);
				}

				if (statusDispose) {
					statusDispose.dispose();
				}
			}
		};
		this.statusMsgDispose = dispose;

		if (typeof autoDisposeAfter === 'number' && autoDisposeAfter > 0) {
			hideHandle = setTimeout(() => dispose.dispose(), autoDisposeAfter);
		}

		return dispose;
	}
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248

	layout(dimension: Dimension): Dimension[];
	layout(width: number, height: number): void;
	layout(dim1: Dimension | number, dim2?: number): Dimension[] | void {
		if (dim1 instanceof Dimension) {
			return super.layout(dim1);
		} else {
			super.layout(new Dimension(dim1, dim2!));
		}
	}

	toJSON(): object {
		return {
			type: Parts.STATUSBAR_PART
		};
	}
E
Erich Gamma 已提交
249 250
}

251
let manageExtensionAction: ManageExtensionAction;
E
Erich Gamma 已提交
252 253 254
class StatusBarEntryItem implements IStatusbarItem {

	constructor(
255
		private entry: IStatusbarEntry,
256 257 258 259 260 261 262
		@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
E
Erich Gamma 已提交
263 264
	) {
		this.entry = entry;
265 266 267 268

		if (!manageExtensionAction) {
			manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction);
		}
E
Erich Gamma 已提交
269 270
	}

B
Benjamin Pasero 已提交
271
	render(el: HTMLElement): IDisposable {
272
		let toDispose: IDisposable[] = [];
273 274
		addClass(el, 'statusbar-entry');

E
Erich Gamma 已提交
275 276 277 278 279
		// Text Container
		let textContainer: HTMLElement;
		if (this.entry.command) {
			textContainer = document.createElement('a');

M
Matt Bierner 已提交
280
			toDispose.push(addDisposableListener(textContainer, 'click', () => this.executeCommand(this.entry.command!, this.entry.arguments)));
E
Erich Gamma 已提交
281 282 283 284
		} else {
			textContainer = document.createElement('span');
		}

285 286
		// Label
		new OcticonLabel(textContainer).text = this.entry.text;
E
Erich Gamma 已提交
287 288 289

		// Tooltip
		if (this.entry.tooltip) {
B
Benjamin Pasero 已提交
290
			textContainer.title = this.entry.tooltip;
E
Erich Gamma 已提交
291 292 293
		}

		// Color
294 295 296 297 298 299 300
		let color = this.entry.color;
		if (color) {
			if (isThemeColor(color)) {
				let colorId = color.id;
				color = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString();
				toDispose.push(this.themeService.onThemeChange(theme => {
					let colorValue = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString();
B
Benjamin Pasero 已提交
301
					textContainer.style.color = colorValue;
302 303
				}));
			}
B
Benjamin Pasero 已提交
304
			textContainer.style.color = color;
E
Erich Gamma 已提交
305 306
		}

307 308
		// Context Menu
		if (this.entry.extensionId) {
B
Benjamin Pasero 已提交
309
			toDispose.push(addDisposableListener(textContainer, 'contextmenu', e => {
310
				EventHelper.stop(e, true);
311 312 313

				this.contextMenuService.showContextMenu({
					getAnchor: () => el,
S
Sandeep Somavarapu 已提交
314
					getActionsContext: () => this.entry.extensionId!.value,
315
					getActions: () => [manageExtensionAction]
316
				});
B
Benjamin Pasero 已提交
317
			}));
318 319
		}

E
Erich Gamma 已提交
320 321 322 323
		el.appendChild(textContainer);

		return {
			dispose: () => {
J
Joao Moreno 已提交
324
				toDispose = dispose(toDispose);
E
Erich Gamma 已提交
325 326 327 328
			}
		};
	}

J
Joao Moreno 已提交
329 330
	private executeCommand(id: string, args?: any[]) {
		args = args || [];
E
Erich Gamma 已提交
331

A
Alex Dima 已提交
332
		// Maintain old behaviour of always focusing the editor here
333 334 335
		const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
		if (activeTextEditorWidget) {
			activeTextEditorWidget.focus();
E
Erich Gamma 已提交
336
		}
A
Alex Dima 已提交
337

338 339 340 341 342 343 344
		/* __GDPR__
			"workbenchActionExecuted" : {
				"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
				"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
			}
		*/
		this.telemetryService.publicLog('workbenchActionExecuted', { id, from: 'status bar' });
345
		this.commandService.executeCommand(id, ...args).then(undefined, err => this.notificationService.error(toErrorMessage(err)));
E
Erich Gamma 已提交
346
	}
A
Alex Dima 已提交
347
}
348 349 350 351

class ManageExtensionAction extends Action {

	constructor(
352
		@ICommandService private readonly commandService: ICommandService
353 354 355 356
	) {
		super('statusbar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
	}

J
Johannes Rieken 已提交
357
	run(extensionId: string): Promise<any> {
358 359
		return this.commandService.executeCommand('_extensions.manage', extensionId);
	}
B
Benjamin Pasero 已提交
360 361 362 363 364
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
	if (statusBarItemHoverBackground) {
365
		collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`);
B
Benjamin Pasero 已提交
366 367 368 369
	}

	const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
	if (statusBarItemActiveBackground) {
370
		collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a:active { background-color: ${statusBarItemActiveBackground}; }`);
B
Benjamin Pasero 已提交
371 372
	}

B
Benjamin Pasero 已提交
373 374
	const statusBarProminentItemBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND);
	if (statusBarProminentItemBackground) {
375
		collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`);
B
Benjamin Pasero 已提交
376 377
	}

B
Benjamin Pasero 已提交
378 379
	const statusBarProminentItemHoverBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND);
	if (statusBarProminentItemHoverBackground) {
380
		collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a.status-bar-info:hover { background-color: ${statusBarProminentItemHoverBackground}; }`);
B
Benjamin Pasero 已提交
381
	}
382
});