statusbarPart.ts 9.7 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 16 17 18 19 20 21 22 23 24 25 26
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';
import { Registry } from 'vs/platform/platform';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Part } from 'vs/workbench/browser/part';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
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 } from 'vs/platform/theme/common/themeService';
E
Erich Gamma 已提交
30 31 32

export class StatusbarPart extends Part implements IStatusbarService {

33
	public _serviceBrand: any;
E
Erich Gamma 已提交
34 35 36 37 38 39

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

	private toDispose: IDisposable[];
	private statusItemsContainer: Builder;
40
	private statusMsgDispose: IDisposable;
E
Erich Gamma 已提交
41 42

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

		this.toDispose = [];
	}

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

		// Render entry in status bar
B
Benjamin Pasero 已提交
55 56 57
		const el = this.doCreateStatusItem(alignment, priority);
		const item = this.instantiationService.createInstance(StatusBarEntryItem, entry);
		const toDispose = item.render(el);
E
Erich Gamma 已提交
58 59

		// Insert according to priority
B
Benjamin Pasero 已提交
60 61
		const container = this.statusItemsContainer.getHTMLElement();
		const neighbours = this.getEntries(alignment);
E
Erich Gamma 已提交
62 63
		let inserted = false;
		for (let i = 0; i < neighbours.length; i++) {
B
Benjamin Pasero 已提交
64 65
			const neighbour = neighbours[i];
			const nPriority = $(neighbour).getProperty(StatusbarPart.PRIORITY_PROP);
E
Erich Gamma 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
			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 已提交
92
		const entries: HTMLElement[] = [];
E
Erich Gamma 已提交
93

B
Benjamin Pasero 已提交
94 95
		const container = this.statusItemsContainer.getHTMLElement();
		const children = container.children;
E
Erich Gamma 已提交
96
		for (let i = 0; i < children.length; i++) {
B
Benjamin Pasero 已提交
97
			const childElement = <HTMLElement>children.item(i);
E
Erich Gamma 已提交
98 99 100 101 102 103 104 105 106 107 108 109
			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 已提交
110
		const registry = Registry.as<IStatusbarRegistry>(Extensions.Statusbar);
E
Erich Gamma 已提交
111

B
Benjamin Pasero 已提交
112 113
		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 已提交
114

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

		this.toDispose.push(...descriptors.map(descriptor => {
B
Benjamin Pasero 已提交
118 119
			const item = this.instantiationService.createInstance(descriptor.syncDescriptor);
			const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority);
E
Erich Gamma 已提交
120

B
Benjamin Pasero 已提交
121
			const dispose = item.render(el);
E
Erich Gamma 已提交
122 123 124 125 126 127 128 129 130
			this.statusItemsContainer.append(el);

			return dispose;
		}));

		return this.statusItemsContainer;
	}

	private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0): HTMLElement {
B
Benjamin Pasero 已提交
131
		const el = document.createElement('div');
E
Erich Gamma 已提交
132 133 134 135 136 137 138 139 140 141 142 143 144 145
		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;
	}

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
	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(() => {
			statusDispose = this.addEntry({ text: message }, StatusbarAlignment.LEFT, Number.MIN_VALUE);
			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 已提交
184
	public dispose(): void {
J
Joao Moreno 已提交
185
		this.toDispose = dispose(this.toDispose);
E
Erich Gamma 已提交
186 187 188 189 190

		super.dispose();
	}
}

191
let manageExtensionAction: ManageExtensionAction;
E
Erich Gamma 已提交
192 193 194 195 196
class StatusBarEntryItem implements IStatusbarItem {
	private entry: IStatusbarEntry;

	constructor(
		entry: IStatusbarEntry,
197
		@ICommandService private commandService: ICommandService,
E
Erich Gamma 已提交
198 199 200
		@IInstantiationService private instantiationService: IInstantiationService,
		@IMessageService private messageService: IMessageService,
		@ITelemetryService private telemetryService: ITelemetryService,
201
		@IContextMenuService private contextMenuService: IContextMenuService,
E
Erich Gamma 已提交
202 203 204
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService
	) {
		this.entry = entry;
205 206 207 208

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

	public render(el: HTMLElement): IDisposable {
212
		let toDispose: IDisposable[] = [];
E
Erich Gamma 已提交
213 214 215 216 217 218 219 220 221 222 223 224
		dom.addClass(el, 'statusbar-entry');

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

			$(textContainer).on('click', () => this.executeCommand(this.entry.command), toDispose);
		} else {
			textContainer = document.createElement('span');
		}

225 226
		// Label
		new OcticonLabel(textContainer).text = this.entry.text;
E
Erich Gamma 已提交
227 228 229 230 231 232 233 234 235 236 237

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

		// Color
		if (this.entry.color) {
			$(textContainer).color(this.entry.color);
		}

238 239 240 241 242 243 244 245 246 247 248 249 250
		// 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 已提交
251 252 253 254
		el.appendChild(textContainer);

		return {
			dispose: () => {
J
Joao Moreno 已提交
255
				toDispose = dispose(toDispose);
E
Erich Gamma 已提交
256 257 258 259 260 261 262
			}
		};
	}

	private executeCommand(id: string) {

		// Lookup built in commands
B
Benjamin Pasero 已提交
263
		const builtInActionDescriptor = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).getWorkbenchAction(id);
E
Erich Gamma 已提交
264
		if (builtInActionDescriptor) {
B
Benjamin Pasero 已提交
265
			const action = this.instantiationService.createInstance(builtInActionDescriptor.syncDescriptor);
E
Erich Gamma 已提交
266 267 268

			if (action.enabled) {
				this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'status bar' });
A
Alex Dima 已提交
269
				(action.run() || TPromise.as(null)).done(() => {
E
Erich Gamma 已提交
270 271 272
					action.dispose();
				}, (err) => this.messageService.show(Severity.Error, toErrorMessage(err)));
			} else {
B
wording  
Benjamin Pasero 已提交
273
				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 已提交
274
			}
A
Alex Dima 已提交
275 276

			return;
E
Erich Gamma 已提交
277 278
		}

A
Alex Dima 已提交
279
		// Maintain old behaviour of always focusing the editor here
B
Benjamin Pasero 已提交
280 281
		const activeEditor = this.editorService.getActiveEditor();
		const codeEditor = getCodeEditor(activeEditor);
A
Alex Dima 已提交
282 283
		if (codeEditor) {
			codeEditor.focus();
E
Erich Gamma 已提交
284
		}
A
Alex Dima 已提交
285 286 287

		// Fallback to the command service for any other case
		this.commandService.executeCommand(id).done(undefined, err => this.messageService.show(Severity.Error, toErrorMessage(err)));
E
Erich Gamma 已提交
288
	}
A
Alex Dima 已提交
289
}
290 291 292 293 294 295 296 297 298 299 300 301 302

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);
	}
}