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

'use strict';

B
Benjamin Pasero 已提交
8
import 'vs/css!./media/statusbarpart';
E
Erich Gamma 已提交
9 10
import dom = require('vs/base/browser/dom');
import nls = require('vs/nls');
J
Johannes Rieken 已提交
11 12 13 14 15
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { TPromise } from 'vs/base/common/winjs.base';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Builder, $ } from 'vs/base/browser/builder';
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
16
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
17 18 19
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Part } from 'vs/workbench/browser/part';
B
Benjamin Pasero 已提交
20
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
J
Johannes Rieken 已提交
21 22 23 24 25 26
import { StatusbarAlignment, IStatusbarRegistry, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { IStatusbarService, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
27 28
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action } from 'vs/base/common/actions';
B
Benjamin Pasero 已提交
29
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
30
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';
31
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
32
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
33
import { isThemeColor } from 'vs/editor/common/editorCommon';
34
import { Color } from 'vs/base/common/color';
E
Erich Gamma 已提交
35 36 37

export class StatusbarPart extends Part implements IStatusbarService {

38
	public _serviceBrand: any;
E
Erich Gamma 已提交
39 40 41 42 43

	private static PRIORITY_PROP = 'priority';
	private static ALIGNMENT_PROP = 'alignment';

	private statusItemsContainer: Builder;
44
	private statusMsgDispose: IDisposable;
E
Erich Gamma 已提交
45 46

	constructor(
47
		id: string,
B
Benjamin Pasero 已提交
48
		@IInstantiationService private instantiationService: IInstantiationService,
49 50
		@IThemeService themeService: IThemeService,
		@IWorkspaceContextService private contextService: IWorkspaceContextService
E
Erich Gamma 已提交
51
	) {
B
Benjamin Pasero 已提交
52
		super(id, { hasTitle: false }, themeService);
E
Erich Gamma 已提交
53

B
Benjamin Pasero 已提交
54 55 56 57
		this.registerListeners();
	}

	private registerListeners(): void {
58
		this.toUnbind.push(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
E
Erich Gamma 已提交
59 60 61 62 63
	}

	public addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable {

		// Render entry in status bar
B
Benjamin Pasero 已提交
64 65 66
		const el = this.doCreateStatusItem(alignment, priority);
		const item = this.instantiationService.createInstance(StatusBarEntryItem, entry);
		const toDispose = item.render(el);
E
Erich Gamma 已提交
67 68

		// Insert according to priority
B
Benjamin Pasero 已提交
69 70
		const container = this.statusItemsContainer.getHTMLElement();
		const neighbours = this.getEntries(alignment);
E
Erich Gamma 已提交
71 72
		let inserted = false;
		for (let i = 0; i < neighbours.length; i++) {
B
Benjamin Pasero 已提交
73 74
			const neighbour = neighbours[i];
			const nPriority = $(neighbour).getProperty(StatusbarPart.PRIORITY_PROP);
E
Erich Gamma 已提交
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
			if (
				alignment === StatusbarAlignment.LEFT && nPriority < priority ||
				alignment === StatusbarAlignment.RIGHT && nPriority > priority
			) {
				container.insertBefore(el, neighbour);
				inserted = true;
				break;
			}
		}

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

		return {
			dispose: () => {
				$(el).destroy();

				if (toDispose) {
					toDispose.dispose();
				}
			}
		};
	}

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

B
Benjamin Pasero 已提交
103 104
		const container = this.statusItemsContainer.getHTMLElement();
		const children = container.children;
E
Erich Gamma 已提交
105
		for (let i = 0; i < children.length; i++) {
B
Benjamin Pasero 已提交
106
			const childElement = <HTMLElement>children.item(i);
E
Erich Gamma 已提交
107 108 109 110 111 112 113 114 115 116 117 118
			if ($(childElement).getProperty(StatusbarPart.ALIGNMENT_PROP) === alignment) {
				entries.push(childElement);
			}
		}

		return entries;
	}

	public createContentArea(parent: Builder): Builder {
		this.statusItemsContainer = $(parent);

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

B
Benjamin Pasero 已提交
121 122
		const leftDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.LEFT).sort((a, b) => b.priority - a.priority);
		const rightDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.RIGHT).sort((a, b) => a.priority - b.priority);
E
Erich Gamma 已提交
123

B
Benjamin Pasero 已提交
124
		const descriptors = rightDescriptors.concat(leftDescriptors); // right first because they float
E
Erich Gamma 已提交
125

B
Benjamin Pasero 已提交
126
		this.toUnbind.push(...descriptors.map(descriptor => {
B
Benjamin Pasero 已提交
127 128
			const item = this.instantiationService.createInstance(descriptor.syncDescriptor);
			const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority);
E
Erich Gamma 已提交
129

B
Benjamin Pasero 已提交
130
			const dispose = item.render(el);
E
Erich Gamma 已提交
131 132 133 134 135 136 137 138
			this.statusItemsContainer.append(el);

			return dispose;
		}));

		return this.statusItemsContainer;
	}

139
	protected updateStyles(): void {
140 141
		super.updateStyles();

142 143
		const container = this.getContainer();

144 145
		container.style('color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND));
		container.style('background-color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND));
B
Benjamin Pasero 已提交
146

147
		const borderColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BORDER : STATUS_BAR_NO_FOLDER_BORDER) || this.getColor(contrastBorder);
148 149 150
		container.style('border-top-width', borderColor ? '1px' : null);
		container.style('border-top-style', borderColor ? 'solid' : null);
		container.style('border-top-color', borderColor);
151 152
	}

E
Erich Gamma 已提交
153
	private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0): HTMLElement {
B
Benjamin Pasero 已提交
154
		const el = document.createElement('div');
E
Erich Gamma 已提交
155 156 157 158 159 160 161 162 163 164 165 166 167 168
		dom.addClass(el, 'statusbar-item');

		if (alignment === StatusbarAlignment.RIGHT) {
			dom.addClass(el, 'right');
		} else {
			dom.addClass(el, 'left');
		}

		$(el).setProperty(StatusbarPart.PRIORITY_PROP, priority);
		$(el).setProperty(StatusbarPart.ALIGNMENT_PROP, alignment);

		return el;
	}

169 170 171 172 173 174 175 176
	public setStatusMessage(message: string, autoDisposeAfter: number = -1, delayBy: number = 0): IDisposable {
		if (this.statusMsgDispose) {
			this.statusMsgDispose.dispose(); // dismiss any previous
		}

		// Create new
		let statusDispose: IDisposable;
		let showHandle = setTimeout(() => {
177
			statusDispose = this.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
			showHandle = null;
		}, delayBy);
		let hideHandle: number;

		// 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;
	}
E
Erich Gamma 已提交
206 207
}

208
let manageExtensionAction: ManageExtensionAction;
E
Erich Gamma 已提交
209 210 211 212 213
class StatusBarEntryItem implements IStatusbarItem {
	private entry: IStatusbarEntry;

	constructor(
		entry: IStatusbarEntry,
214
		@ICommandService private commandService: ICommandService,
E
Erich Gamma 已提交
215 216 217
		@IInstantiationService private instantiationService: IInstantiationService,
		@IMessageService private messageService: IMessageService,
		@ITelemetryService private telemetryService: ITelemetryService,
218
		@IContextMenuService private contextMenuService: IContextMenuService,
219 220
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IThemeService private themeService: IThemeService
E
Erich Gamma 已提交
221 222
	) {
		this.entry = entry;
223 224 225 226

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

	public render(el: HTMLElement): IDisposable {
230
		let toDispose: IDisposable[] = [];
E
Erich Gamma 已提交
231 232 233 234 235 236 237
		dom.addClass(el, 'statusbar-entry');

		// Text Container
		let textContainer: HTMLElement;
		if (this.entry.command) {
			textContainer = document.createElement('a');

J
Joao Moreno 已提交
238
			$(textContainer).on('click', () => this.executeCommand(this.entry.command, this.entry.arguments), toDispose);
E
Erich Gamma 已提交
239 240 241 242
		} else {
			textContainer = document.createElement('span');
		}

243 244
		// Label
		new OcticonLabel(textContainer).text = this.entry.text;
E
Erich Gamma 已提交
245 246 247 248 249 250 251

		// Tooltip
		if (this.entry.tooltip) {
			$(textContainer).title(this.entry.tooltip);
		}

		// Color
252 253 254 255 256 257 258 259 260 261 262
		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();
					$(textContainer).color(colorValue);
				}));
			}
			$(textContainer).color(color);
E
Erich Gamma 已提交
263 264
		}

265 266 267 268 269 270 271 272 273 274 275 276 277
		// Context Menu
		if (this.entry.extensionId) {
			$(textContainer).on('contextmenu', e => {
				dom.EventHelper.stop(e, true);

				this.contextMenuService.showContextMenu({
					getAnchor: () => el,
					getActionsContext: () => this.entry.extensionId,
					getActions: () => TPromise.as([manageExtensionAction])
				});
			}, toDispose);
		}

E
Erich Gamma 已提交
278 279 280 281
		el.appendChild(textContainer);

		return {
			dispose: () => {
J
Joao Moreno 已提交
282
				toDispose = dispose(toDispose);
E
Erich Gamma 已提交
283 284 285 286
			}
		};
	}

J
Joao Moreno 已提交
287 288
	private executeCommand(id: string, args?: any[]) {
		args = args || [];
E
Erich Gamma 已提交
289 290

		// Lookup built in commands
B
Benjamin Pasero 已提交
291
		const builtInActionDescriptor = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).getWorkbenchAction(id);
E
Erich Gamma 已提交
292
		if (builtInActionDescriptor) {
B
Benjamin Pasero 已提交
293
			const action = this.instantiationService.createInstance(builtInActionDescriptor.syncDescriptor);
E
Erich Gamma 已提交
294 295 296

			if (action.enabled) {
				this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'status bar' });
A
Alex Dima 已提交
297
				(action.run() || TPromise.as(null)).done(() => {
E
Erich Gamma 已提交
298 299 300
					action.dispose();
				}, (err) => this.messageService.show(Severity.Error, toErrorMessage(err)));
			} else {
B
wording  
Benjamin Pasero 已提交
301
				this.messageService.show(Severity.Warning, nls.localize('canNotRun', "Command '{0}' is currently not enabled and can not be run.", action.label || id));
E
Erich Gamma 已提交
302
			}
A
Alex Dima 已提交
303 304

			return;
E
Erich Gamma 已提交
305 306
		}

A
Alex Dima 已提交
307
		// Maintain old behaviour of always focusing the editor here
B
Benjamin Pasero 已提交
308 309
		const activeEditor = this.editorService.getActiveEditor();
		const codeEditor = getCodeEditor(activeEditor);
A
Alex Dima 已提交
310 311
		if (codeEditor) {
			codeEditor.focus();
E
Erich Gamma 已提交
312
		}
A
Alex Dima 已提交
313 314

		// Fallback to the command service for any other case
J
Joao Moreno 已提交
315
		this.commandService.executeCommand(id, ...args).done(undefined, err => this.messageService.show(Severity.Error, toErrorMessage(err)));
E
Erich Gamma 已提交
316
	}
A
Alex Dima 已提交
317
}
318 319 320 321 322 323 324 325 326 327 328 329

class ManageExtensionAction extends Action {

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

	public run(extensionId: string): TPromise<any> {
		return this.commandService.executeCommand('_extensions.manage', extensionId);
	}
B
Benjamin Pasero 已提交
330 331 332 333 334 335 336 337 338 339 340 341 342
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
	if (statusBarItemHoverBackground) {
		collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item a:hover:not([disabled]):not(.disabled) { background-color: ${statusBarItemHoverBackground}; }`);
	}

	const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
	if (statusBarItemActiveBackground) {
		collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item a:active:not([disabled]):not(.disabled) { background-color: ${statusBarItemActiveBackground}; }`);
	}

B
Benjamin Pasero 已提交
343 344 345
	const statusBarProminentItemBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND);
	if (statusBarProminentItemBackground) {
		collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`);
B
Benjamin Pasero 已提交
346 347
	}

B
Benjamin Pasero 已提交
348 349 350
	const statusBarProminentItemHoverBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND);
	if (statusBarProminentItemHoverBackground) {
		collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item a.status-bar-info:hover:not([disabled]):not(.disabled) { background-color: ${statusBarProminentItemHoverBackground}; }`);
B
Benjamin Pasero 已提交
351 352
	}
});