actions.ts 30.8 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';

8
import URI from 'vs/base/common/uri';
J
Johannes Rieken 已提交
9 10
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
J
Joao Moreno 已提交
11
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
12
import { IWindowService, IWindowsService, MenuBarVisibility } from 'vs/platform/windows/common/windows';
J
Johannes Rieken 已提交
13
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
E
Erich Gamma 已提交
14
import nls = require('vs/nls');
15 16
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
B
Benjamin Pasero 已提交
17
import errors = require('vs/base/common/errors');
J
Johannes Rieken 已提交
18 19 20 21
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
22
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
J
Johannes Rieken 已提交
23 24 25
import { IExtensionManagementService, LocalExtensionType, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
B
Benjamin Pasero 已提交
26
import paths = require('vs/base/common/paths');
27
import { isMacintosh, isLinux } from 'vs/base/common/platform';
J
Johannes Rieken 已提交
28
import { IQuickOpenService, IFilePickOpenEntry, ISeparator } from 'vs/platform/quickOpen/common/quickOpen';
J
Johannes Rieken 已提交
29 30
import { KeyMod } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
31
import * as browser from 'vs/base/browser/browser';
J
Johannes Rieken 已提交
32
import { IIntegrityService } from 'vs/platform/integrity/common/integrity';
B
Benjamin Pasero 已提交
33
import { IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen';
B
Benjamin Pasero 已提交
34
import { ITimerService, IStartupMetrics } from 'vs/workbench/services/timer/common/timerService';
E
Erich Gamma 已提交
35

B
Benjamin Pasero 已提交
36
import * as os from 'os';
37
import { webFrame } from 'electron';
E
Erich Gamma 已提交
38

39 40
// --- actions

E
Erich Gamma 已提交
41 42 43 44 45 46 47 48
export class CloseEditorAction extends Action {

	public static ID = 'workbench.action.closeActiveEditor';
	public static LABEL = nls.localize('closeActiveEditor', "Close Editor");

	constructor(
		id: string,
		label: string,
49
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService
E
Erich Gamma 已提交
50 51 52 53
	) {
		super(id, label);
	}

54
	public run(): TPromise<any> {
B
Benjamin Pasero 已提交
55
		const activeEditor = this.editorService.getActiveEditor();
E
Erich Gamma 已提交
56
		if (activeEditor) {
57
			return this.editorService.closeEditor(activeEditor.position, activeEditor.input);
E
Erich Gamma 已提交
58 59
		}

A
Alex Dima 已提交
60
		return TPromise.as(false);
E
Erich Gamma 已提交
61 62 63 64 65 66 67 68
	}
}

export class CloseWindowAction extends Action {

	public static ID = 'workbench.action.closeWindow';
	public static LABEL = nls.localize('closeWindow', "Close Window");

J
Joao Moreno 已提交
69
	constructor(id: string, label: string, @IWindowIPCService private windowService: IWindowIPCService) {
E
Erich Gamma 已提交
70 71 72
		super(id, label);
	}

73
	public run(): TPromise<boolean> {
E
Erich Gamma 已提交
74 75
		this.windowService.getWindow().close();

A
Alex Dima 已提交
76
		return TPromise.as(true);
E
Erich Gamma 已提交
77 78 79
	}
}

80
export class SwitchWindow extends Action {
81

J
Joao Moreno 已提交
82 83
	static ID = 'workbench.action.switchWindow';
	static LABEL = nls.localize('switchWindow', "Switch Window");
84 85 86 87

	constructor(
		id: string,
		label: string,
J
Joao Moreno 已提交
88
		@IWindowsService private windowsService: IWindowsService,
J
Joao Moreno 已提交
89
		@IWindowService private windowService: IWindowService,
90 91 92 93 94
		@IQuickOpenService private quickOpenService: IQuickOpenService
	) {
		super(id, label);
	}

J
Joao Moreno 已提交
95 96
	run(): TPromise<void> {
		const currentWindowId = this.windowService.getCurrentWindowId();
97

J
Joao Moreno 已提交
98 99 100 101 102 103 104 105 106 107
		return this.windowsService.getWindows().then(workspaces => {
			const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window");
			const picks = workspaces.map(w => ({
				label: w.title,
				description: (currentWindowId === w.id) ? nls.localize('current', "Current Window") : void 0,
				run: () => this.windowsService.showWindow(w.id)
			}));

			this.quickOpenService.pick(picks, { placeHolder });
		});
108 109 110
	}
}

E
Erich Gamma 已提交
111 112
export class CloseFolderAction extends Action {

J
Joao Moreno 已提交
113 114
	static ID = 'workbench.action.closeFolder';
	static LABEL = nls.localize('closeFolder', "Close Folder");
E
Erich Gamma 已提交
115 116 117 118 119

	constructor(
		id: string,
		label: string,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
B
Benjamin Pasero 已提交
120
		@IMessageService private messageService: IMessageService,
J
Joao Moreno 已提交
121
		@IWindowService private windowService: IWindowService
E
Erich Gamma 已提交
122 123 124 125
	) {
		super(id, label);
	}

J
Joao Moreno 已提交
126
	run(): TPromise<void> {
B
Benjamin Pasero 已提交
127
		if (!this.contextService.hasWorkspace()) {
E
Erich Gamma 已提交
128
			this.messageService.show(Severity.Info, nls.localize('noFolderOpened', "There is currently no folder opened in this instance to close."));
J
Joao Moreno 已提交
129
			return TPromise.as(null);
E
Erich Gamma 已提交
130 131
		}

J
Joao Moreno 已提交
132
		return this.windowService.closeFolder();
E
Erich Gamma 已提交
133 134 135 136 137
	}
}

export class NewWindowAction extends Action {

J
Joao Moreno 已提交
138 139
	static ID = 'workbench.action.newWindow';
	static LABEL = nls.localize('newWindow', "New Window");
E
Erich Gamma 已提交
140

B
Benjamin Pasero 已提交
141 142 143
	constructor(
		id: string,
		label: string,
J
Joao Moreno 已提交
144
		@IWindowsService private windowsService: IWindowsService
B
Benjamin Pasero 已提交
145
	) {
E
Erich Gamma 已提交
146 147 148
		super(id, label);
	}

J
Joao Moreno 已提交
149 150
	run(): TPromise<void> {
		return this.windowsService.openNewWindow();
E
Erich Gamma 已提交
151 152 153 154 155
	}
}

export class ToggleFullScreenAction extends Action {

J
Joao Moreno 已提交
156 157
	static ID = 'workbench.action.toggleFullScreen';
	static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen");
E
Erich Gamma 已提交
158

J
Joao Moreno 已提交
159
	constructor(id: string, label: string, @IWindowService private windowService: IWindowService) {
E
Erich Gamma 已提交
160 161 162
		super(id, label);
	}

J
Joao Moreno 已提交
163 164
	run(): TPromise<void> {
		return this.windowService.toggleFullScreen();
E
Erich Gamma 已提交
165 166 167
	}
}

168 169
export class ToggleMenuBarAction extends Action {

J
Joao Moreno 已提交
170 171
	static ID = 'workbench.action.toggleMenuBar';
	static LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar");
172

173
	private static menuBarVisibilityKey = 'window.menuBarVisibility';
174 175 176 177 178 179 180 181

	constructor(
		id: string,
		label: string,
		@IMessageService private messageService: IMessageService,
		@IConfigurationService private configurationService: IConfigurationService,
		@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
	) {
182 183 184
		super(id, label);
	}

185
	public run(): TPromise<any> {
186 187 188
		let currentVisibilityValue = this.configurationService.lookup<MenuBarVisibility>(ToggleMenuBarAction.menuBarVisibilityKey).value;
		if (typeof currentVisibilityValue !== 'string') {
			currentVisibilityValue = 'default';
189 190 191
		}

		let newVisibilityValue: string;
192
		if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
193 194
			newVisibilityValue = 'toggle';
		} else {
195
			newVisibilityValue = 'default';
196 197 198 199 200 201 202
		}

		this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleMenuBarAction.menuBarVisibilityKey, value: newVisibilityValue }).then(null, error => {
			this.messageService.show(Severity.Error, error);
		});

		return TPromise.as(null);
203 204 205
	}
}

E
Erich Gamma 已提交
206 207
export class ToggleDevToolsAction extends Action {

J
Joao Moreno 已提交
208 209
	static ID = 'workbench.action.toggleDevTools';
	static LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools");
E
Erich Gamma 已提交
210

J
Joao Moreno 已提交
211
	constructor(id: string, label: string, @IWindowService private windowsService: IWindowService) {
E
Erich Gamma 已提交
212 213 214
		super(id, label);
	}

B
Benjamin Pasero 已提交
215
	public run(): TPromise<void> {
J
Joao Moreno 已提交
216
		return this.windowsService.toggleDevTools();
E
Erich Gamma 已提交
217 218 219
	}
}

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
export abstract class BaseZoomAction extends Action {
	private static SETTING_KEY = 'window.zoomLevel';

	constructor(
		id: string,
		label: string,
		@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
		@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
	) {
		super(id, label);
	}

	protected setConfiguredZoomLevel(level: number): void {
		let target = ConfigurationTarget.USER;
		if (typeof this.configurationService.lookup(BaseZoomAction.SETTING_KEY).workspace === 'number') {
			target = ConfigurationTarget.WORKSPACE;
		}

238 239
		level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels

B
Benjamin Pasero 已提交
240
		const applyZoom = () => {
241
			webFrame.setZoomLevel(level);
B
Benjamin Pasero 已提交
242
			browser.setZoomFactor(webFrame.getZoomFactor());
243
			browser.setZoomLevel(level); // Ensure others can listen to zoom level changes
244 245
		};

B
Benjamin Pasero 已提交
246
		this.configurationEditingService.writeConfiguration(target, { key: BaseZoomAction.SETTING_KEY, value: level }).done(() => applyZoom(), error => applyZoom());
247 248 249 250
	}
}

export class ZoomInAction extends BaseZoomAction {
E
Erich Gamma 已提交
251 252

	public static ID = 'workbench.action.zoomIn';
B
Benjamin Pasero 已提交
253
	public static LABEL = nls.localize('zoomIn', "Zoom In");
E
Erich Gamma 已提交
254

255 256 257 258 259 260
	constructor(
		id: string,
		label: string,
		@IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService,
		@IConfigurationEditingService configurationEditingService: IConfigurationEditingService
	) {
B
Benjamin Pasero 已提交
261
		super(id, label, configurationService, configurationEditingService);
E
Erich Gamma 已提交
262 263
	}

264
	public run(): TPromise<boolean> {
265
		this.setConfiguredZoomLevel(webFrame.getZoomLevel() + 1);
E
Erich Gamma 已提交
266

A
Alex Dima 已提交
267
		return TPromise.as(true);
E
Erich Gamma 已提交
268 269 270
	}
}

271
export class ZoomOutAction extends BaseZoomAction {
E
Erich Gamma 已提交
272 273

	public static ID = 'workbench.action.zoomOut';
B
Benjamin Pasero 已提交
274
	public static LABEL = nls.localize('zoomOut', "Zoom Out");
E
Erich Gamma 已提交
275

276 277
	constructor(
		id: string,
278 279 280
		label: string,
		@IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService,
		@IConfigurationEditingService configurationEditingService: IConfigurationEditingService
281
	) {
B
Benjamin Pasero 已提交
282
		super(id, label, configurationService, configurationEditingService);
E
Erich Gamma 已提交
283 284
	}

285
	public run(): TPromise<boolean> {
286
		this.setConfiguredZoomLevel(webFrame.getZoomLevel() - 1);
287

288
		return TPromise.as(true);
E
Erich Gamma 已提交
289 290 291
	}
}

292
export class ZoomResetAction extends BaseZoomAction {
E
Erich Gamma 已提交
293 294 295 296

	public static ID = 'workbench.action.zoomReset';
	public static LABEL = nls.localize('zoomReset', "Reset Zoom");

297 298 299
	constructor(
		id: string,
		label: string,
300 301
		@IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService,
		@IConfigurationEditingService configurationEditingService: IConfigurationEditingService
302
	) {
B
Benjamin Pasero 已提交
303
		super(id, label, configurationService, configurationEditingService);
E
Erich Gamma 已提交
304 305
	}

306
	public run(): TPromise<boolean> {
307
		this.setConfiguredZoomLevel(0);
308

309
		return TPromise.as(true);
E
Erich Gamma 已提交
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
	}
}

/* Copied from loader.ts */
enum LoaderEventType {
	LoaderAvailable = 1,

	BeginLoadingScript = 10,
	EndLoadingScriptOK = 11,
	EndLoadingScriptError = 12,

	BeginInvokeFactory = 21,
	EndInvokeFactory = 22,

	NodeBeginEvaluatingScript = 31,
	NodeEndEvaluatingScript = 32,

	NodeBeginNativeRequire = 33,
	NodeEndNativeRequire = 34
}
330

E
Erich Gamma 已提交
331 332 333 334 335
interface ILoaderEvent {
	type: LoaderEventType;
	timestamp: number;
	detail: string;
}
336

E
Erich Gamma 已提交
337 338 339 340 341
export class ShowStartupPerformance extends Action {

	public static ID = 'workbench.action.appPerf';
	public static LABEL = nls.localize('appPerf', "Startup Performance");

342 343 344
	constructor(
		id: string,
		label: string,
J
Joao Moreno 已提交
345
		@IWindowService private windowService: IWindowService,
B
Benjamin Pasero 已提交
346
		@ITimerService private timerService: ITimerService,
347
		@IEnvironmentService private environmentService: IEnvironmentService
348
	) {
E
Erich Gamma 已提交
349
		super(id, label);
350 351 352 353 354 355 356 357 358
	}

	public run(): TPromise<boolean> {

		// Show dev tools
		this.windowService.openDevTools();

		// Print to console
		setTimeout(() => {
359
			(<any>console).group('Startup Performance Measurement');
B
Benjamin Pasero 已提交
360 361 362 363 364
			const metrics: IStartupMetrics = this.timerService.startupMetrics;
			console.log(`OS: ${metrics.platform} (${metrics.release})`);
			console.log(`CPUs: ${metrics.cpus.model} (${metrics.cpus.count} x ${metrics.cpus.speed})`);
			console.log(`Memory (System): ${(metrics.totalmem / (1024 * 1024 * 1024)).toFixed(2)}GB (${(metrics.freemem / (1024 * 1024 * 1024)).toFixed(2)}GB free)`);
			console.log(`Memory (Process): ${(metrics.meminfo.workingSetSize / 1024).toFixed(2)}MB working set (${(metrics.meminfo.peakWorkingSetSize / 1024).toFixed(2)}MB peak, ${(metrics.meminfo.privateBytes / 1024).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / 1024).toFixed(2)}MB shared)`);
365
			console.log(`VM (likelyhood): ${metrics.isVMLikelyhood}%`);
B
Benjamin Pasero 已提交
366 367 368
			console.log(`Initial Startup: ${metrics.initialStartup}`);
			console.log(`Screen Reader Active: ${metrics.hasAccessibilitySupport}`);
			console.log(`Empty Workspace: ${metrics.emptyWorkbench}`);
369 370 371 372 373 374 375 376 377

			let nodeModuleLoadTime: number;
			let nodeModuleLoadDetails: any[];
			if (this.environmentService.performance) {
				const nodeModuleTimes = this.analyzeNodeModulesLoadTimes();
				nodeModuleLoadTime = nodeModuleTimes.duration;
				nodeModuleLoadDetails = nodeModuleTimes.table;
			}

B
Benjamin Pasero 已提交
378
			(<any>console).table(this.getStartupMetricsTable(nodeModuleLoadTime));
379

380 381 382 383 384 385 386
			if (this.environmentService.performance) {
				const data = this.analyzeLoaderStats();
				for (let type in data) {
					(<any>console).groupCollapsed(`Loader: ${type}`);
					(<any>console).table(data[type]);
					(<any>console).groupEnd();
				}
387
			}
388

389 390
			(<any>console).groupEnd();
		}, 1000);
391

392
		return TPromise.as(true);
E
Erich Gamma 已提交
393 394
	}

B
Benjamin Pasero 已提交
395
	private getStartupMetricsTable(nodeModuleLoadTime?: number): any[] {
396
		const table: any[] = [];
B
Benjamin Pasero 已提交
397
		const metrics: IStartupMetrics = this.timerService.startupMetrics;
398

B
Benjamin Pasero 已提交
399
		if (metrics.initialStartup) {
400 401
			table.push({ Topic: '[main] start => app.isReady', 'Took (ms)': metrics.timers.ellapsedAppReady });
			table.push({ Topic: '[main] app.isReady => window.loadUrl()', 'Took (ms)': metrics.timers.ellapsedWindowLoad });
E
Erich Gamma 已提交
402
		}
403

B
Benjamin Pasero 已提交
404 405
		table.push({ Topic: '[renderer] window.loadUrl() => begin to require(workbench.main.js)', 'Took (ms)': metrics.timers.ellapsedWindowLoadToRequire });
		table.push({ Topic: '[renderer] require(workbench.main.js)', 'Took (ms)': metrics.timers.ellapsedRequire });
406 407 408 409 410

		if (nodeModuleLoadTime) {
			table.push({ Topic: '[renderer] -> of which require() node_modules', 'Took (ms)': nodeModuleLoadTime });
		}

B
Benjamin Pasero 已提交
411 412 413 414
		table.push({ Topic: '[renderer] create extension host => extensions onReady()', 'Took (ms)': metrics.timers.ellapsedExtensions });
		table.push({ Topic: '[renderer] restore viewlet', 'Took (ms)': metrics.timers.ellapsedViewletRestore });
		table.push({ Topic: '[renderer] restore editor view state', 'Took (ms)': metrics.timers.ellapsedEditorRestore });
		table.push({ Topic: '[renderer] overall workbench load', 'Took (ms)': metrics.timers.ellapsedWorkbench });
415
		table.push({ Topic: '------------------------------------------------------' });
B
Benjamin Pasero 已提交
416 417
		table.push({ Topic: '[main, renderer] start => extensions ready', 'Took (ms)': metrics.timers.ellapsedExtensionsReady });
		table.push({ Topic: '[main, renderer] start => workbench ready', 'Took (ms)': metrics.ellapsed });
E
Erich Gamma 已提交
418

419
		return table;
E
Erich Gamma 已提交
420 421
	}

422
	private analyzeNodeModulesLoadTimes(): { table: any[], duration: number } {
423 424 425 426 427 428 429 430 431
		const stats = <ILoaderEvent[]>(<any>require).getStats();
		const result = [];

		let total = 0;

		for (let i = 0, len = stats.length; i < len; i++) {
			if (stats[i].type === LoaderEventType.NodeEndNativeRequire) {
				if (stats[i - 1].type === LoaderEventType.NodeBeginNativeRequire && stats[i - 1].detail === stats[i].detail) {
					const entry: any = {};
432
					const dur = (stats[i].timestamp - stats[i - 1].timestamp);
433
					entry['Event'] = 'nodeRequire ' + stats[i].detail;
434 435 436 437
					entry['Took (ms)'] = dur.toFixed(2);
					total += dur;
					entry['Start (ms)'] = '**' + stats[i - 1].timestamp.toFixed(2);
					entry['End (ms)'] = '**' + stats[i - 1].timestamp.toFixed(2);
438 439 440 441 442 443
					result.push(entry);
				}
			}
		}

		if (total > 0) {
444 445
			result.push({ Event: '------------------------------------------------------' });

446
			const entry: any = {};
447 448
			entry['Event'] = '[renderer] total require() node_modules';
			entry['Took (ms)'] = total.toFixed(2);
449 450 451 452 453
			entry['Start (ms)'] = '**';
			entry['End (ms)'] = '**';
			result.push(entry);
		}

454
		return { table: result, duration: Math.round(total) };
E
Erich Gamma 已提交
455
	}
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563

	private analyzeLoaderStats(): { [type: string]: any[] } {
		const stats = <ILoaderEvent[]>(<any>require).getStats().slice(0).sort((a, b) => {
			if (a.detail < b.detail) {
				return -1;
			} else if (a.detail > b.detail) {
				return 1;
			} else if (a.type < b.type) {
				return -1;
			} else if (a.type > b.type) {
				return 1;
			} else {
				return 0;
			}
		});

		class Tick {

			public readonly duration: number;
			public readonly detail: string;

			constructor(public readonly start: ILoaderEvent, public readonly end: ILoaderEvent) {
				console.assert(start.detail === end.detail);

				this.duration = this.end.timestamp - this.start.timestamp;
				this.detail = start.detail;
			}

			toTableObject() {
				return {
					['Path']: this.start.detail,
					['Took (ms)']: this.duration.toFixed(2),
					// ['Start (ms)']: this.start.timestamp,
					// ['End (ms)']: this.end.timestamp
				};
			}

			static compareUsingStartTimestamp(a: Tick, b: Tick): number {
				if (a.start.timestamp < b.start.timestamp) {
					return -1;
				} else if (a.start.timestamp > b.start.timestamp) {
					return 1;
				} else {
					return 0;
				}
			}
		}

		const ticks: { [type: number]: Tick[] } = {
			[LoaderEventType.BeginLoadingScript]: [],
			[LoaderEventType.BeginInvokeFactory]: [],
			[LoaderEventType.NodeBeginEvaluatingScript]: [],
			[LoaderEventType.NodeBeginNativeRequire]: [],
		};

		for (let i = 1; i < stats.length - 1; i++) {
			const stat = stats[i];
			const nextStat = stats[i + 1];

			if (nextStat.type - stat.type > 2) {
				//bad?!
				break;
			}

			i += 1;
			ticks[stat.type].push(new Tick(stat, nextStat));
		}

		ticks[LoaderEventType.BeginInvokeFactory].sort(Tick.compareUsingStartTimestamp);
		ticks[LoaderEventType.BeginInvokeFactory].sort(Tick.compareUsingStartTimestamp);
		ticks[LoaderEventType.NodeBeginEvaluatingScript].sort(Tick.compareUsingStartTimestamp);
		ticks[LoaderEventType.NodeBeginNativeRequire].sort(Tick.compareUsingStartTimestamp);

		const ret = {
			'Load Script': ticks[LoaderEventType.BeginLoadingScript].map(t => t.toTableObject()),
			'(Node) Load Script': ticks[LoaderEventType.NodeBeginNativeRequire].map(t => t.toTableObject()),
			'Eval Script': ticks[LoaderEventType.BeginInvokeFactory].map(t => t.toTableObject()),
			'(Node) Eval Script': ticks[LoaderEventType.NodeBeginEvaluatingScript].map(t => t.toTableObject()),
		};

		function total(ticks: Tick[]): number {
			let sum = 0;
			for (const tick of ticks) {
				sum += tick.duration;
			}
			return sum;
		}

		// totals
		ret['Load Script'].push({
			['Path']: 'TOTAL TIME',
			['Took (ms)']: total(ticks[LoaderEventType.BeginLoadingScript]).toFixed(2)
		});
		ret['Eval Script'].push({
			['Path']: 'TOTAL TIME',
			['Took (ms)']: total(ticks[LoaderEventType.BeginInvokeFactory]).toFixed(2)
		});
		ret['(Node) Load Script'].push({
			['Path']: 'TOTAL TIME',
			['Took (ms)']: total(ticks[LoaderEventType.NodeBeginNativeRequire]).toFixed(2)
		});
		ret['(Node) Eval Script'].push({
			['Path']: 'TOTAL TIME',
			['Took (ms)']: total(ticks[LoaderEventType.NodeBeginEvaluatingScript]).toFixed(2)
		});

		return ret;
	}
E
Erich Gamma 已提交
564 565 566 567
}

export class ReloadWindowAction extends Action {

568 569
	static ID = 'workbench.action.reloadWindow';
	static LABEL = nls.localize('reloadWindow', "Reload Window");
E
Erich Gamma 已提交
570

571 572 573
	constructor(
		id: string,
		label: string,
B
Benjamin Pasero 已提交
574
		@IWindowService private windowService: IWindowService
575
	) {
E
Erich Gamma 已提交
576 577 578
		super(id, label);
	}

579 580
	run(): TPromise<boolean> {
		return this.windowService.reloadWindow().then(() => true);
E
Erich Gamma 已提交
581 582 583 584 585 586 587 588 589 590 591
	}
}

export class OpenRecentAction extends Action {

	public static ID = 'workbench.action.openRecent';
	public static LABEL = nls.localize('openRecent', "Open Recent");

	constructor(
		id: string,
		label: string,
592
		@IWindowsService private windowsService: IWindowsService,
J
Joao Moreno 已提交
593
		@IWindowService private windowService: IWindowService,
B
Benjamin Pasero 已提交
594 595
		@IQuickOpenService private quickOpenService: IQuickOpenService,
		@IWorkspaceContextService private contextService: IWorkspaceContextService
E
Erich Gamma 已提交
596 597 598 599
	) {
		super(id, label);
	}

J
Joao Moreno 已提交
600 601 602
	public run(): TPromise<void> {
		return this.windowService.getRecentlyOpen()
			.then(({ files, folders }) => this.openRecent(files, folders));
B
Benjamin Pasero 已提交
603 604 605
	}

	private openRecent(recentFiles: string[], recentFolders: string[]): void {
B
Benjamin Pasero 已提交
606
		function toPick(path: string, separator: ISeparator, isFolder: boolean): IFilePickOpenEntry {
B
Benjamin Pasero 已提交
607
			return {
B
Benjamin Pasero 已提交
608 609
				resource: URI.file(path),
				isFolder,
B
Benjamin Pasero 已提交
610 611 612
				label: paths.basename(path),
				description: paths.dirname(path),
				separator,
B
Benjamin Pasero 已提交
613
				run: context => runPick(path, context)
B
Benjamin Pasero 已提交
614 615 616
			};
		}

B
Benjamin Pasero 已提交
617
		const runPick = (path: string, context: IEntryRunContext) => {
618 619
			const forceNewWindow = context.keymods.indexOf(KeyMod.CtrlCmd) >= 0;
			this.windowsService.openWindow([path], { forceNewWindow });
620
		};
B
Benjamin Pasero 已提交
621

B
Benjamin Pasero 已提交
622 623
		const folderPicks: IFilePickOpenEntry[] = recentFolders.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('folders', "folders") } : void 0, true));
		const filePicks: IFilePickOpenEntry[] = recentFiles.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('files', "files"), border: true } : void 0, false));
B
Benjamin Pasero 已提交
624

B
Benjamin Pasero 已提交
625
		const hasWorkspace = this.contextService.hasWorkspace();
B
Benjamin Pasero 已提交
626 627 628 629 630 631

		this.quickOpenService.pick(folderPicks.concat(...filePicks), {
			autoFocus: { autoFocusFirstEntry: !hasWorkspace, autoFocusSecondEntry: hasWorkspace },
			placeHolder: isMacintosh ? nls.localize('openRecentPlaceHolderMac', "Select a path (hold Cmd-key to open in new window)") : nls.localize('openRecentPlaceHolder', "Select a path to open (hold Ctrl-key to open in new window)"),
			matchOnDescription: true
		}).done(null, errors.onUnexpectedError);
E
Erich Gamma 已提交
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
	}
}

export class CloseMessagesAction extends Action {

	public static ID = 'workbench.action.closeMessages';
	public static LABEL = nls.localize('closeMessages', "Close Notification Messages");

	constructor(
		id: string,
		label: string,
		@IMessageService private messageService: IMessageService,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService
	) {
		super(id, label);
	}

649
	public run(): TPromise<boolean> {
E
Erich Gamma 已提交
650 651 652 653 654 655 656 657 658 659

		// Close any Message if visible
		this.messageService.hideAll();

		// Restore focus if we got an editor
		const editor = this.editorService.getActiveEditor();
		if (editor) {
			editor.focus();
		}

660
		return TPromise.as(true);
E
Erich Gamma 已提交
661
	}
662 663
}

B
Benjamin Pasero 已提交
664 665 666 667 668 669 670 671 672
export class ReportIssueAction extends Action {

	public static ID = 'workbench.action.reportIssues';
	public static LABEL = nls.localize('reportIssues', "Report Issues");

	constructor(
		id: string,
		label: string,
		@IIntegrityService private integrityService: IIntegrityService,
B
Benjamin Pasero 已提交
673
		@IExtensionManagementService private extensionManagementService: IExtensionManagementService
B
Benjamin Pasero 已提交
674 675 676 677 678 679
	) {
		super(id, label);
	}

	public run(): TPromise<boolean> {
		return this.integrityService.isPure().then(res => {
B
Benjamin Pasero 已提交
680 681
			return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
				const issueUrl = this.generateNewIssueUrl(product.reportIssueUrl, pkg.name, pkg.version, product.commit, product.date, res.isPure, extensions);
B
Benjamin Pasero 已提交
682

B
Benjamin Pasero 已提交
683
				window.open(issueUrl);
B
Benjamin Pasero 已提交
684

B
Benjamin Pasero 已提交
685 686
				return TPromise.as(true);
			});
B
Benjamin Pasero 已提交
687 688 689
		});
	}

J
Johannes Rieken 已提交
690
	private generateNewIssueUrl(baseUrl: string, name: string, version: string, commit: string, date: string, isPure: boolean, extensions: ILocalExtension[]): string {
C
Christof Marti 已提交
691
		// Avoid backticks, these can trigger XSS detectors. (https://github.com/Microsoft/vscode/issues/13098)
B
Benjamin Pasero 已提交
692 693 694 695 696
		const osVersion = `${os.type()} ${os.arch()} ${os.release()}`;
		const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&';
		const body = encodeURIComponent(
			`- VSCode Version: ${name} ${version}${isPure ? '' : ' **[Unsupported]**'} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})
- OS Version: ${osVersion}
697 698 699 700 701
- Extensions:

${this.generateExtensionTable(extensions)}

---
B
Benjamin Pasero 已提交
702 703 704 705 706 707 708 709 710

Steps to Reproduce:

1.
2.`
		);

		return `${baseUrl}${queryStringPrefix}body=${body}`;
	}
711 712

	private generateExtensionTable(extensions: ILocalExtension[]): string {
713 714
		let tableHeader = `|Extension|Author|Version|
|---|---|---|`;
715
		const table = extensions.map(e => {
716
			return `|${e.manifest.name}|${e.manifest.publisher}|${e.manifest.version}|`;
717 718
		}).join('\n');

B
Benjamin Pasero 已提交
719
		return `${tableHeader}\n${table}`;
720
	}
B
Benjamin Pasero 已提交
721 722
}

723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764
export class ReportPerformanceIssueAction extends Action {

	public static ID = 'workbench.action.reportPerformanceIssue';
	public static LABEL = nls.localize('reportPerformanceIssue', "Report Performance Issue");

	constructor(
		id: string,
		label: string,
		@IIntegrityService private integrityService: IIntegrityService,
		@IEnvironmentService private environmentService: IEnvironmentService,
		@ITimerService private timerService: ITimerService
	) {
		super(id, label);
	}

	public run(): TPromise<boolean> {
		return this.integrityService.isPure().then(res => {
			const issueUrl = this.generatePerformanceIssueUrl(product.reportIssueUrl, pkg.name, pkg.version, product.commit, product.date, res.isPure);

			window.open(issueUrl);

			return TPromise.as(true);
		});
	}

	private generatePerformanceIssueUrl(baseUrl: string, name: string, version: string, commit: string, date: string, isPure: boolean): string {
		let nodeModuleLoadTime: number;
		if (this.environmentService.performance) {
			nodeModuleLoadTime = this.computeNodeModulesLoadTime();
		}

		const metrics: IStartupMetrics = this.timerService.startupMetrics;

		const osVersion = `${os.type()} ${os.arch()} ${os.release()}`;
		const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&';
		const body = encodeURIComponent(
			`- VSCode Version: <code>${name} ${version}${isPure ? '' : ' **[Unsupported]**'} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})</code>
- OS Version: <code>${osVersion}</code>
- CPUs: <code>${metrics.cpus.model} (${metrics.cpus.count} x ${metrics.cpus.speed})</code>
- Memory (System): <code>${(metrics.totalmem / (1024 * 1024 * 1024)).toFixed(2)}GB (${(metrics.freemem / (1024 * 1024 * 1024)).toFixed(2)}GB free)</code>
- Memory (Process): <code>${(metrics.meminfo.workingSetSize / 1024).toFixed(2)}MB working set (${(metrics.meminfo.peakWorkingSetSize / 1024).toFixed(2)}MB peak, ${(metrics.meminfo.privateBytes / 1024).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / 1024).toFixed(2)}MB shared)</code>
- Load (avg): <code>${metrics.loadavg.map(l => Math.round(l)).join(', ')}</code>
765
- VM: <code>${metrics.isVMLikelyhood}%</code>
766
- Initial Startup: <code>${metrics.initialStartup ? 'yes' : 'no'}</code>
767
- Screen Reader: <code>${metrics.hasAccessibilitySupport ? 'yes' : 'no'}</code>
768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
- Empty Workspace: <code>${metrics.emptyWorkbench ? 'yes' : 'no'}</code>
- Timings:

${this.generatePerformanceTable(nodeModuleLoadTime)}

---

Additional Steps to Reproduce (if any):

1.
2.`
		);

		return `${baseUrl}${queryStringPrefix}body=${body}`;
	}

	private computeNodeModulesLoadTime(): number {
		const stats = <ILoaderEvent[]>(<any>require).getStats();
		let total = 0;

		for (let i = 0, len = stats.length; i < len; i++) {
			if (stats[i].type === LoaderEventType.NodeEndNativeRequire) {
				if (stats[i - 1].type === LoaderEventType.NodeBeginNativeRequire && stats[i - 1].detail === stats[i].detail) {
					const dur = (stats[i].timestamp - stats[i - 1].timestamp);
					total += dur;
				}
			}
		}

		return Math.round(total);
	}

	private generatePerformanceTable(nodeModuleLoadTime?: number): string {
		let tableHeader = `|Component|Task|Time (ms)|
|---|---|---|`;

		const table = this.getStartupMetricsTable(nodeModuleLoadTime).map(e => {
805
			return `|${e.component}|${e.task}|${e.time}|`;
806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
		}).join('\n');

		return `${tableHeader}\n${table}`;
	}

	private getStartupMetricsTable(nodeModuleLoadTime?: number): { component: string, task: string; time: number; }[] {
		const table: any[] = [];
		const metrics: IStartupMetrics = this.timerService.startupMetrics;

		if (metrics.initialStartup) {
			table.push({ component: 'main', task: 'start => app.isReady', time: metrics.timers.ellapsedAppReady });
			table.push({ component: 'main', task: 'app.isReady => window.loadUrl()', time: metrics.timers.ellapsedWindowLoad });
		}

		table.push({ component: 'renderer', task: 'window.loadUrl() => begin to require(workbench.main.js)', time: metrics.timers.ellapsedWindowLoadToRequire });
		table.push({ component: 'renderer', task: 'require(workbench.main.js)', time: metrics.timers.ellapsedRequire });

		if (nodeModuleLoadTime) {
			table.push({ component: 'renderer', task: '-> of which require() node_modules', time: nodeModuleLoadTime });
		}

		table.push({ component: 'renderer', task: 'create extension host => extensions onReady()', time: metrics.timers.ellapsedExtensions });
		table.push({ component: 'renderer', task: 'restore viewlet', time: metrics.timers.ellapsedViewletRestore });
		table.push({ component: 'renderer', task: 'restore editor view state', time: metrics.timers.ellapsedEditorRestore });
		table.push({ component: 'renderer', task: 'overall workbench load', time: metrics.timers.ellapsedWorkbench });
		table.push({ component: 'main + renderer', task: 'start => extensions ready', time: metrics.timers.ellapsedExtensionsReady });
		table.push({ component: 'main + renderer', task: 'start => workbench ready', time: metrics.ellapsed });

		return table;
	}
}

838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
export class KeybindingsReferenceAction extends Action {

	public static ID = 'workbench.action.keybindingsReference';
	public static LABEL = nls.localize('keybindingsReference', "Keyboard Shortcuts Reference");

	private static URL = isLinux ? product.keyboardShortcutsUrlLinux : isMacintosh ? product.keyboardShortcutsUrlMac : product.keyboardShortcutsUrlWin;
	public static AVAILABLE = !!KeybindingsReferenceAction.URL;

	constructor(
		id: string,
		label: string
	) {
		super(id, label);
	}

	public run(): TPromise<void> {
		window.open(KeybindingsReferenceAction.URL);
		return null;
	}
}

C
Christof Marti 已提交
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
export class OpenDocumentationUrlAction extends Action {

	public static ID = 'workbench.action.openDocumentationUrl';
	public static LABEL = nls.localize('openDocumentationUrl', "Documentation");

	private static URL = product.documentationUrl;
	public static AVAILABLE = !!OpenDocumentationUrlAction.URL;

	constructor(
		id: string,
		label: string
	) {
		super(id, label);
	}

	public run(): TPromise<void> {
		window.open(OpenDocumentationUrlAction.URL);
		return null;
	}
}

880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
export class OpenIntroductoryVideosUrlAction extends Action {

	public static ID = 'workbench.action.openIntroductoryVideosUrl';
	public static LABEL = nls.localize('openIntroductoryVideosUrl', "Introductory Videos");

	private static URL = product.introductoryVideosUrl;
	public static AVAILABLE = !!OpenIntroductoryVideosUrlAction.URL;

	constructor(
		id: string,
		label: string
	) {
		super(id, label);
	}

	public run(): TPromise<void> {
		window.open(OpenIntroductoryVideosUrlAction.URL);
		return null;
	}
}

901 902
// --- commands

903
CommandsRegistry.registerCommand('_workbench.diff', function (accessor: ServicesAccessor, args: [URI, URI, string, string]) {
904
	const editorService = accessor.get(IWorkbenchEditorService);
905
	let [leftResource, rightResource, label, description] = args;
906

907
	if (!label) {
908
		label = nls.localize('diffLeftRightLabel', "{0} ⟷ {1}", leftResource.toString(true), rightResource.toString(true));
909
	}
910

911
	return editorService.openEditor({ leftResource, rightResource, label, description }).then(() => {
912 913
		return void 0;
	});
914 915
});

916 917
CommandsRegistry.registerCommand('_workbench.open', function (accessor: ServicesAccessor, args: [URI, number]) {
	const editorService = accessor.get(IWorkbenchEditorService);
B
Benjamin Pasero 已提交
918
	const [resource, column] = args;
919

920 921 922
	return editorService.openEditor({ resource }, column).then(() => {
		return void 0;
	});
923
});