welcomePage.ts 30.5 KB
Newer Older
C
Christof Marti 已提交
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.
 *--------------------------------------------------------------------------------------------*/

6
import 'vs/css!./welcomePage';
B
Benjamin Pasero 已提交
7
import 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page';
8
import { URI } from 'vs/base/common/uri';
9
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
10
import * as arrays from 'vs/base/common/arrays';
B
Benjamin Pasero 已提交
11
import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
C
Christof Marti 已提交
12 13
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
14
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
15
import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors';
16
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
17
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
18
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
C
Christof Marti 已提交
19
import { localize } from 'vs/nls';
20
import { Action, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
C
Christof Marti 已提交
21
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
22
import { Schemas } from 'vs/base/common/network';
23
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
24
import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/contrib/extensions/common/extensionsUtils';
25
import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
26
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
C
Christof Marti 已提交
27
import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle';
28
import { Disposable } from 'vs/base/common/lifecycle';
M
Martin Aeschlimann 已提交
29
import { splitName } from 'vs/base/common/labels';
C
Christof Marti 已提交
30
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
31
import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
32
import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils';
33
import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
34
import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor';
35
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
36
import { TimeoutTimer } from 'vs/base/common/async';
S
Sandeep Somavarapu 已提交
37
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
I
isidor 已提交
38
import { ILabelService } from 'vs/platform/label/common/label';
C
Christof Marti 已提交
39
import { IFileService } from 'vs/platform/files/common/files';
40
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
M
Martin Aeschlimann 已提交
41
import { joinPath } from 'vs/base/common/resources';
42
import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
S
Sandeep Somavarapu 已提交
43
import { CancellationToken } from 'vs/base/common/cancellation';
44
import { IHostService } from 'vs/workbench/services/host/browser/host';
45
import { IProductService } from 'vs/platform/product/common/productService';
46
import { IEditorOptions } from 'vs/platform/editor/common/editor';
E
Eric Amodio 已提交
47
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
48
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
49

50 51
const configurationKey = 'workbench.startupEditor';
const oldConfigurationKey = 'workbench.welcome.enabled';
52
const telemetryFrom = 'welcomePage';
C
Christof Marti 已提交
53 54 55 56 57 58

export class WelcomePageContribution implements IWorkbenchContribution {

	constructor(
		@IInstantiationService instantiationService: IInstantiationService,
		@IConfigurationService configurationService: IConfigurationService,
59
		@IEditorService editorService: IEditorService,
60
		@IBackupFileService backupFileService: IBackupFileService,
C
Christof Marti 已提交
61
		@IFileService fileService: IFileService,
62
		@IWorkspaceContextService contextService: IWorkspaceContextService,
63
		@ILifecycleService lifecycleService: ILifecycleService,
E
Eric Amodio 已提交
64
		@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
65
		@ICommandService private readonly commandService: ICommandService,
C
Christof Marti 已提交
66
	) {
67
		const enabled = isWelcomePageEnabled(configurationService, contextService);
C
Christof Marti 已提交
68
		if (enabled && lifecycleService.startupKind !== StartupKind.ReloadedWindow) {
69
			backupFileService.hasBackups().then(hasBackups => {
E
Eric Amodio 已提交
70 71
				// Open the welcome even if we opened a set of default editors
				if ((!editorService.activeEditor || layoutService.openedDefaultEditors) && !hasBackups) {
72 73
					const openWithReadme = configurationService.getValue(configurationKey) === 'readme';
					if (openWithReadme) {
C
Christof Marti 已提交
74 75
						return Promise.all(contextService.getWorkspace().folders.map(folder => {
							const folderUri = folder.uri;
B
Benjamin Pasero 已提交
76
							return fileService.resolve(folderUri)
77 78 79
								.then(folder => {
									const files = folder.children ? folder.children.map(child => child.name) : [];

80
									const file = files.sort().find(file => file.toLowerCase().startsWith('readme'));
C
Christof Marti 已提交
81
									if (file) {
M
Martin Aeschlimann 已提交
82
										return joinPath(folderUri, file);
C
Christof Marti 已提交
83 84 85
									}
									return undefined;
								}, onUnexpectedError);
M
Matt Bierner 已提交
86
						})).then(arrays.coalesce)
C
Christof Marti 已提交
87 88 89
							.then<any>(readmes => {
								if (!editorService.activeEditor) {
									if (readmes.length) {
90
										const isMarkDown = (readme: URI) => readme.path.toLowerCase().endsWith('.md');
C
Christof Marti 已提交
91 92 93 94 95 96 97
										return Promise.all([
											this.commandService.executeCommand('markdown.showPreview', null, readmes.filter(isMarkDown), { locked: true }),
											editorService.openEditors(readmes.filter(readme => !isMarkDown(readme))
												.map(readme => ({ resource: readme }))),
										]);
									} else {
										return instantiationService.createInstance(WelcomePage).openEditor();
98 99 100 101 102
									}
								}
								return undefined;
							});
					} else {
103 104 105 106 107 108 109 110 111 112 113
						let options: IEditorOptions;
						let editor = editorService.activeEditor;
						if (editor) {
							// Ensure that the welcome editor won't get opened more than once
							if (editor.getTypeId() === welcomeInputTypeId || editorService.editors.some(e => e.getTypeId() === welcomeInputTypeId)) {
								return undefined;
							}
							options = { pinned: false, index: 0 };
						} else {
							options = { pinned: false };
						}
114
						return instantiationService.createInstance(WelcomePage).openEditor(options);
115
					}
116
				}
117
				return undefined;
R
Rob Lourens 已提交
118
			}).then(undefined, onUnexpectedError);
C
Christof Marti 已提交
119 120 121 122
		}
	}
}

C
Christof Marti 已提交
123
function isWelcomePageEnabled(configurationService: IConfigurationService, contextService: IWorkspaceContextService) {
124
	const startupEditor = configurationService.inspect(configurationKey);
S
rename  
Sandeep Somavarapu 已提交
125
	if (!startupEditor.userValue && !startupEditor.workspaceValue) {
126
		const welcomeEnabled = configurationService.inspect(oldConfigurationKey);
127 128 129 130
		if (welcomeEnabled.value !== undefined && welcomeEnabled.value !== null) {
			return welcomeEnabled.value;
		}
	}
131
	return startupEditor.value === 'welcomePage' || startupEditor.value === 'readme' || startupEditor.value === 'welcomePageInEmptyWorkbench' && contextService.getWorkbenchState() === WorkbenchState.EMPTY;
132 133
}

C
Christof Marti 已提交
134 135
export class WelcomePageAction extends Action {

M
Matt Bierner 已提交
136 137
	public static readonly ID = 'workbench.action.showWelcomePage';
	public static readonly LABEL = localize('welcomePage', "Welcome");
C
Christof Marti 已提交
138 139 140 141

	constructor(
		id: string,
		label: string,
142
		@IInstantiationService private readonly instantiationService: IInstantiationService
C
Christof Marti 已提交
143 144 145 146
	) {
		super(id, label);
	}

J
Johannes Rieken 已提交
147
	public run(): Promise<void> {
148 149 150
		return this.instantiationService.createInstance(WelcomePage)
			.openEditor()
			.then(() => undefined);
C
Christof Marti 已提交
151 152 153
	}
}

154 155
interface ExtensionSuggestion {
	name: string;
C
Christof Marti 已提交
156
	title?: string;
157 158
	id: string;
	isKeymap?: boolean;
C
Christof Marti 已提交
159
	isCommand?: boolean;
160 161 162 163
}

const extensionPacks: ExtensionSuggestion[] = [
	{ name: localize('welcomePage.javaScript', "JavaScript"), id: 'dbaeumer.vscode-eslint' },
164
	{ name: localize('welcomePage.python', "Python"), id: 'ms-python.python' },
S
sana-ajani 已提交
165
	{ name: localize('welcomePage.java', "Java"), id: 'vscjava.vscode-java-pack' },
166
	{ name: localize('welcomePage.php', "PHP"), id: 'felixfbecker.php-pack' },
C
Christof Marti 已提交
167
	{ name: localize('welcomePage.azure', "Azure"), title: localize('welcomePage.showAzureExtensions', "Show Azure extensions"), id: 'workbench.extensions.action.showAzureExtensions', isCommand: true },
S
Sandeep Somavarapu 已提交
168
	{ name: localize('welcomePage.docker', "Docker"), id: 'ms-azuretools.vscode-docker' },
169 170 171 172 173 174 175 176 177 178 179
];

const keymapExtensions: ExtensionSuggestion[] = [
	{ name: localize('welcomePage.vim', "Vim"), id: 'vscodevim.vim', isKeymap: true },
	{ name: localize('welcomePage.sublime', "Sublime"), id: 'ms-vscode.sublime-keybindings', isKeymap: true },
	{ name: localize('welcomePage.atom', "Atom"), id: 'ms-vscode.atom-keybindings', isKeymap: true },
];

interface Strings {
	installEvent: string;
	installedEvent: string;
180
	detailsEvent: string;
181 182 183 184 185 186 187

	alreadyInstalled: string;
	reloadAfterInstall: string;
	installing: string;
	extensionNotFound: string;
}

K
kieferrm 已提交
188
/* __GDPR__
K
kieferrm 已提交
189 190 191 192 193 194
	"installExtension" : {
		"${include}": [
			"${WelcomePageInstall-1}"
		]
	}
*/
K
kieferrm 已提交
195
/* __GDPR__
K
kieferrm 已提交
196 197 198 199 200 201 202 203 204 205
	"installedExtension" : {
		"${include}": [
			"${WelcomePageInstalled-1}",
			"${WelcomePageInstalled-2}",
			"${WelcomePageInstalled-3}",
			"${WelcomePageInstalled-4}",
			"${WelcomePageInstalled-6}"
		]
	}
*/
K
kieferrm 已提交
206
/* __GDPR__
K
kieferrm 已提交
207 208 209 210 211 212
	"detailsExtension" : {
		"${include}": [
			"${WelcomePageDetails-1}"
		]
	}
*/
213 214 215
const extensionPackStrings: Strings = {
	installEvent: 'installExtension',
	installedEvent: 'installedExtension',
216
	detailsEvent: 'detailsExtension',
217 218

	alreadyInstalled: localize('welcomePage.extensionPackAlreadyInstalled', "Support for {0} is already installed."),
219 220
	reloadAfterInstall: localize('welcomePage.willReloadAfterInstallingExtensionPack', "The window will reload after installing additional support for {0}."),
	installing: localize('welcomePage.installingExtensionPack', "Installing additional support for {0}..."),
221 222 223
	extensionNotFound: localize('welcomePage.extensionPackNotFound', "Support for {0} with id {1} could not be found."),
};

224 225 226 227 228 229 230 231 232 233
CommandsRegistry.registerCommand('workbench.extensions.action.showAzureExtensions', accessor => {
	const viewletService = accessor.get(IViewletService);
	return viewletService.openViewlet(VIEWLET_ID, true)
		.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
		.then(viewlet => {
			viewlet.search('@sort:installs azure ');
			viewlet.focus();
		});
});

K
kieferrm 已提交
234
/* __GDPR__
K
kieferrm 已提交
235 236 237 238 239 240
	"installKeymap" : {
		"${include}": [
			"${WelcomePageInstall-1}"
		]
	}
*/
K
kieferrm 已提交
241
/* __GDPR__
K
kieferrm 已提交
242 243 244 245 246 247 248 249 250 251
	"installedKeymap" : {
		"${include}": [
			"${WelcomePageInstalled-1}",
			"${WelcomePageInstalled-2}",
			"${WelcomePageInstalled-3}",
			"${WelcomePageInstalled-4}",
			"${WelcomePageInstalled-6}"
		]
	}
*/
K
kieferrm 已提交
252
/* __GDPR__
K
kieferrm 已提交
253 254 255 256 257 258
	"detailsKeymap" : {
		"${include}": [
			"${WelcomePageDetails-1}"
		]
	}
*/
259 260 261
const keymapStrings: Strings = {
	installEvent: 'installKeymap',
	installedEvent: 'installedKeymap',
262
	detailsEvent: 'detailsKeymap',
263 264 265 266 267 268 269

	alreadyInstalled: localize('welcomePage.keymapAlreadyInstalled', "The {0} keyboard shortcuts are already installed."),
	reloadAfterInstall: localize('welcomePage.willReloadAfterInstallingKeymap', "The window will reload after installing the {0} keyboard shortcuts."),
	installing: localize('welcomePage.installingKeymap', "Installing the {0} keyboard shortcuts..."),
	extensionNotFound: localize('welcomePage.keymapNotFound', "The {0} keyboard shortcuts with id {1} could not be found."),
};

270 271
const welcomeInputTypeId = 'workbench.editors.welcomePageInput';

272
class WelcomePage extends Disposable {
273

274 275
	readonly editorInput: WalkThroughInput;

C
Christof Marti 已提交
276
	constructor(
277 278
		@IEditorService private readonly editorService: IEditorService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
279
		@IWorkspacesService private readonly workspacesService: IWorkspacesService,
280 281 282 283
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@ILabelService private readonly labelService: ILabelService,
		@INotificationService private readonly notificationService: INotificationService,
S
rename  
Sandeep Somavarapu 已提交
284
		@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
285 286
		@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
		@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
287
		@IExtensionRecommendationsService private readonly tipsService: IExtensionRecommendationsService,
288
		@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
289
		@ILifecycleService lifecycleService: ILifecycleService,
290
		@ITelemetryService private readonly telemetryService: ITelemetryService,
291 292 293
		@IHostService private readonly hostService: IHostService,
		@IProductService private readonly productService: IProductService,

C
Christof Marti 已提交
294
	) {
295 296
		super();
		this._register(lifecycleService.onShutdown(() => this.dispose()));
C
Christof Marti 已提交
297

298
		const recentlyOpened = this.workspacesService.getRecentlyOpened();
299
		const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions);
300
		const resource = URI.parse(require.toUrl('./vs_code_welcome_page'))
301 302
			.with({
				scheme: Schemas.walkThrough,
303
				query: JSON.stringify({ moduleId: 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page' })
304
			});
305 306 307 308 309
		this.editorInput = this.instantiationService.createInstance(WalkThroughInput, {
			typeId: welcomeInputTypeId,
			name: localize('welcome.title', "Welcome"),
			resource,
			telemetryFrom,
310
			onReady: (container: HTMLElement) => this.onReady(container, recentlyOpened, installedExtensions)
311 312 313
		});
	}

314 315
	public openEditor(options: IEditorOptions = { pinned: false }) {
		return this.editorService.openEditor(this.editorInput, options);
C
Christof Marti 已提交
316 317
	}

M
Martin Aeschlimann 已提交
318
	private onReady(container: HTMLElement, recentlyOpened: Promise<IRecentlyOpened>, installedExtensions: Promise<IExtensionStatus[]>): void {
319
		const enabled = isWelcomePageEnabled(this.configurationService, this.contextService);
C
Christof Marti 已提交
320 321 322 323 324
		const showOnStartup = <HTMLInputElement>container.querySelector('#showOnStartup');
		if (enabled) {
			showOnStartup.setAttribute('checked', 'checked');
		}
		showOnStartup.addEventListener('click', e => {
325
			this.configurationService.updateValue(configurationKey, showOnStartup.checked ? 'welcomePage' : 'newUntitledFile', ConfigurationTarget.USER);
C
Christof Marti 已提交
326 327
		});

328 329
		const prodName = container.querySelector('.welcomePage .title .caption') as HTMLElement;
		if (prodName) {
C
Christof Marti 已提交
330
			prodName.textContent = this.productService.nameLong;
331 332
		}

333
		recentlyOpened.then(({ workspaces }) => {
334
			// Filter out the current workspace
M
Martin Aeschlimann 已提交
335
			workspaces = workspaces.filter(recent => !this.contextService.isCurrentWorkspace(isRecentWorkspace(recent) ? recent.workspace : recent.folderUri));
336
			if (!workspaces.length) {
337 338
				const recent = container.querySelector('.welcomePage') as HTMLElement;
				recent.classList.add('emptyRecent');
C
Christof Marti 已提交
339 340
				return;
			}
C
Christof Marti 已提交
341
			const ul = container.querySelector('.recent ul');
M
Matt Bierner 已提交
342 343 344
			if (!ul) {
				return;
			}
C
Christof Marti 已提交
345
			const moreRecent = ul.querySelector('.moreRecent')!;
346 347 348 349 350
			const workspacesToShow = workspaces.slice(0, 5);
			const updateEntries = () => {
				const listEntries = this.createListEntries(workspacesToShow);
				while (ul.firstChild) {
					ul.removeChild(ul.firstChild);
B
Benjamin Pasero 已提交
351
				}
352 353 354
				ul.append(...listEntries, moreRecent);
			};
			updateEntries();
355
			this._register(this.labelService.onDidChangeFormatters(updateEntries));
R
Rob Lourens 已提交
356
		}).then(undefined, onUnexpectedError);
357

358 359
		this.addExtensionList(container, '.extensionPackList', extensionPacks, extensionPackStrings);
		this.addExtensionList(container, '.keymapList', keymapExtensions, keymapStrings);
360

361
		this.updateInstalledExtensions(container, installedExtensions);
362
		this._register(this.instantiationService.invokeFunction(onExtensionChanged)(ids => {
363
			for (const id of ids) {
364
				if (container.querySelector(`.installExtension[data-extension="${id.id}"], .enabledExtension[data-extension="${id.id}"]`)) {
365 366
					const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions);
					this.updateInstalledExtensions(container, installedExtensions);
367 368
					break;
				}
369
			}
370
		}));
371 372
	}

M
Martin Aeschlimann 已提交
373 374
	private createListEntries(recents: (IRecentWorkspace | IRecentFolder)[]) {
		return recents.map(recent => {
M
Martin Aeschlimann 已提交
375
			let fullPath: string;
376
			let windowOpenable: IWindowOpenable;
M
Martin Aeschlimann 已提交
377
			if (isRecentFolder(recent)) {
378
				windowOpenable = { folderUri: recent.folderUri };
M
Martin Aeschlimann 已提交
379
				fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri, { verbose: true });
380
			} else {
M
Martin Aeschlimann 已提交
381
				fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
382
				windowOpenable = { workspaceUri: recent.workspace.configPath };
383 384
			}

M
Martin Aeschlimann 已提交
385
			const { name, parentPath } = splitName(fullPath);
386

M
Martin Aeschlimann 已提交
387
			const li = document.createElement('li');
388 389 390
			const a = document.createElement('a');

			a.innerText = name;
M
Martin Aeschlimann 已提交
391 392
			a.title = fullPath;
			a.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath));
393 394
			a.href = 'javascript:void(0)';
			a.addEventListener('click', e => {
395
				this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', {
396 397 398
					id: 'openRecentFolder',
					from: telemetryFrom
				});
399
				this.hostService.openWindow([windowOpenable], { forceNewWindow: e.ctrlKey || e.metaKey });
400 401 402 403 404 405 406 407
				e.preventDefault();
				e.stopPropagation();
			});
			li.appendChild(a);

			const span = document.createElement('span');
			span.classList.add('path');
			span.classList.add('detail');
M
Martin Aeschlimann 已提交
408 409
			span.innerText = parentPath;
			span.title = fullPath;
410 411 412 413 414 415
			li.appendChild(span);

			return li;
		});
	}

416 417 418 419 420 421 422 423 424 425
	private addExtensionList(container: HTMLElement, listSelector: string, suggestions: ExtensionSuggestion[], strings: Strings) {
		const list = container.querySelector(listSelector);
		if (list) {
			suggestions.forEach((extension, i) => {
				if (i) {
					list.appendChild(document.createTextNode(localize('welcomePage.extensionListSeparator', ", ")));
				}

				const a = document.createElement('a');
				a.innerText = extension.name;
C
Christof Marti 已提交
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
				a.title = extension.title || (extension.isKeymap ? localize('welcomePage.installKeymap', "Install {0} keymap", extension.name) : localize('welcomePage.installExtensionPack', "Install additional support for {0}", extension.name));
				if (extension.isCommand) {
					a.href = `command:${extension.id}`;
					list.appendChild(a);
				} else {
					a.classList.add('installExtension');
					a.setAttribute('data-extension', extension.id);
					a.href = 'javascript:void(0)';
					a.addEventListener('click', e => {
						this.installExtension(extension, strings);
						e.preventDefault();
						e.stopPropagation();
					});
					list.appendChild(a);

					const span = document.createElement('span');
					span.innerText = extension.name;
					span.title = extension.isKeymap ? localize('welcomePage.installedKeymap', "{0} keymap is already installed", extension.name) : localize('welcomePage.installedExtensionPack', "{0} support is already installed", extension.name);
					span.classList.add('enabledExtension');
					span.setAttribute('data-extension', extension.id);
					list.appendChild(span);
				}
448 449 450 451 452
			});
		}
	}

	private installExtension(extensionSuggestion: ExtensionSuggestion, strings: Strings): void {
K
kieferrm 已提交
453
		/* __GDPR__FRAGMENT__
K
kieferrm 已提交
454 455 456 457 458
			"WelcomePageInstall-1" : {
				"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
				"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
			}
		*/
459
		this.telemetryService.publicLog(strings.installEvent, {
460
			from: telemetryFrom,
461
			extensionId: extensionSuggestion.id,
462
		});
463
		this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
464
			const installedExtension = extensions.find(extension => areSameExtensions(extension.identifier, { id: extensionSuggestion.id }));
465
			if (installedExtension && installedExtension.globallyEnabled) {
K
kieferrm 已提交
466
				/* __GDPR__FRAGMENT__
K
kieferrm 已提交
467 468 469 470 471 472
					"WelcomePageInstalled-1" : {
						"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
						"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
						"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
473
				this.telemetryService.publicLog(strings.installedEvent, {
474
					from: telemetryFrom,
475
					extensionId: extensionSuggestion.id,
476 477
					outcome: 'already_enabled',
				});
478
				this.notificationService.info(strings.alreadyInstalled.replace('{0}', extensionSuggestion.name));
479 480
				return;
			}
S
Sandeep Somavarapu 已提交
481
			const foundAndInstalled = installedExtension ? Promise.resolve(installedExtension.local) : this.extensionGalleryService.query({ names: [extensionSuggestion.id], source: telemetryFrom }, CancellationToken.None)
M
Matt Bierner 已提交
482
				.then((result): null | Promise<ILocalExtension | null> => {
483 484
					const [extension] = result.firstPage;
					if (!extension) {
485
						return null;
486
					}
S
Sandeep Somavarapu 已提交
487
					return this.extensionManagementService.installFromGallery(extension)
488
						.then(() => this.extensionManagementService.getInstalled(ExtensionType.User))
S
Sandeep Somavarapu 已提交
489
						.then(installed => {
490
							const local = installed.filter(i => areSameExtensions(extension.identifier, i.identifier))[0];
491
							// TODO: Do this as part of the install to avoid multiple events.
492
							return this.extensionEnablementService.setEnablement([local], EnablementState.DisabledGlobally).then(() => local);
493 494
						});
				});
495

496 497 498 499 500 501
			this.notificationService.prompt(
				Severity.Info,
				strings.reloadAfterInstall.replace('{0}', extensionSuggestion.name),
				[{
					label: localize('ok', "OK"),
					run: () => {
502 503
						const messageDelay = new TimeoutTimer();
						messageDelay.cancelAndSet(() => {
504
							this.notificationService.info(strings.installing.replace('{0}', extensionSuggestion.name));
505
						}, 300);
S
Sandeep Somavarapu 已提交
506
						const extensionsToDisable = extensions.filter(extension => isKeymapExtension(this.tipsService, extension) && extension.globallyEnabled).map(extension => extension.local);
507
						extensionsToDisable.length ? this.extensionEnablementService.setEnablement(extensionsToDisable, EnablementState.DisabledGlobally) : Promise.resolve()
S
Sandeep Somavarapu 已提交
508
							.then(() => {
509
								return foundAndInstalled.then(foundExtension => {
510
									messageDelay.cancel();
511
									if (foundExtension) {
512
										return this.extensionEnablementService.setEnablement([foundExtension], EnablementState.EnabledGlobally)
513
											.then(() => {
K
kieferrm 已提交
514
												/* __GDPR__FRAGMENT__
K
kieferrm 已提交
515 516 517 518 519 520
													"WelcomePageInstalled-2" : {
														"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
														"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
														"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
													}
												*/
521
												this.telemetryService.publicLog(strings.installedEvent, {
522
													from: telemetryFrom,
523 524
													extensionId: extensionSuggestion.id,
													outcome: installedExtension ? 'enabled' : 'installed',
525
												});
526
												return this.hostService.reload();
527
											});
528
									} else {
K
kieferrm 已提交
529
										/* __GDPR__FRAGMENT__
K
kieferrm 已提交
530 531 532 533 534 535
											"WelcomePageInstalled-3" : {
												"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
												"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
												"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
											}
										*/
536
										this.telemetryService.publicLog(strings.installedEvent, {
537
											from: telemetryFrom,
538
											extensionId: extensionSuggestion.id,
539 540
											outcome: 'not_found',
										});
541
										this.notificationService.error(strings.extensionNotFound.replace('{0}', extensionSuggestion.name).replace('{1}', extensionSuggestion.id));
542 543 544
										return undefined;
									}
								});
R
Rob Lourens 已提交
545
							}).then(undefined, err => {
K
kieferrm 已提交
546
								/* __GDPR__FRAGMENT__
K
kieferrm 已提交
547 548 549
									"WelcomePageInstalled-4" : {
										"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
										"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
C
Christof Marti 已提交
550
										"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
K
kieferrm 已提交
551 552
									}
								*/
553
								this.telemetryService.publicLog(strings.installedEvent, {
554
									from: telemetryFrom,
555
									extensionId: extensionSuggestion.id,
556 557
									outcome: isPromiseCanceledError(err) ? 'canceled' : 'error',
								});
558
								this.notificationService.error(err);
559
							});
560 561 562 563
					}
				}, {
					label: localize('details', "Details"),
					run: () => {
K
kieferrm 已提交
564
						/* __GDPR__FRAGMENT__
K
kieferrm 已提交
565 566 567 568 569
							"WelcomePageDetails-1" : {
								"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
								"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
							}
						*/
570 571 572 573
						this.telemetryService.publicLog(strings.detailsEvent, {
							from: telemetryFrom,
							extensionId: extensionSuggestion.id,
						});
S
Sandeep Somavarapu 已提交
574
						this.extensionsWorkbenchService.queryGallery({ names: [extensionSuggestion.id] }, CancellationToken.None)
575
							.then(result => this.extensionsWorkbenchService.open(result.firstPage[0]))
R
Rob Lourens 已提交
576
							.then(undefined, onUnexpectedError);
577 578 579
					}
				}]
			);
R
Rob Lourens 已提交
580
		}).then(undefined, err => {
K
kieferrm 已提交
581
			/* __GDPR__FRAGMENT__
K
kieferrm 已提交
582 583 584
				"WelcomePageInstalled-6" : {
					"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
					"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
C
Christof Marti 已提交
585
					"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
K
kieferrm 已提交
586 587
				}
			*/
588
			this.telemetryService.publicLog(strings.installedEvent, {
589
				from: telemetryFrom,
590
				extensionId: extensionSuggestion.id,
591 592
				outcome: isPromiseCanceledError(err) ? 'canceled' : 'error',
			});
593
			this.notificationService.error(err);
594
		});
C
Christof Marti 已提交
595
	}
596

C
Christof Marti 已提交
597
	private updateInstalledExtensions(container: HTMLElement, installedExtensions: Promise<IExtensionStatus[]>) {
598 599
		installedExtensions.then(extensions => {
			const elements = container.querySelectorAll('.installExtension, .enabledExtension');
600 601 602 603
			for (let i = 0; i < elements.length; i++) {
				elements[i].classList.remove('installed');
			}
			extensions.filter(ext => ext.globallyEnabled)
604
				.map(ext => ext.identifier.id)
605
				.forEach(id => {
606 607 608
					const install = container.querySelectorAll(`.installExtension[data-extension="${id}"]`);
					for (let i = 0; i < install.length; i++) {
						install[i].classList.add('installed');
609
					}
610 611 612
					const enabled = container.querySelectorAll(`.enabledExtension[data-extension="${id}"]`);
					for (let i = 0; i < enabled.length; i++) {
						enabled[i].classList.add('installed');
613 614
					}
				});
R
Rob Lourens 已提交
615
		}).then(undefined, onUnexpectedError);
616
	}
C
Christof Marti 已提交
617
}
618

619 620
export class WelcomeInputFactory implements IEditorInputFactory {

621
	static readonly ID = welcomeInputTypeId;
622

623 624 625 626
	public canSerialize(editorInput: EditorInput): boolean {
		return true;
	}

627 628 629 630 631 632 633 634 635 636
	public serialize(editorInput: EditorInput): string {
		return '{}';
	}

	public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): WalkThroughInput {
		return instantiationService.createInstance(WelcomePage)
			.editorInput;
	}
}

637 638
// theming

639 640
export const buttonBackground = registerColor('welcomePage.buttonBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonBackground', 'Background color for the buttons on the Welcome page.'));
export const buttonHoverBackground = registerColor('welcomePage.buttonHoverBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonHoverBackground', 'Hover background color for the buttons on the Welcome page.'));
641
export const welcomePageBackground = registerColor('welcomePage.background', { light: null, dark: null, hc: null }, localize('welcomePage.background', 'Background color for the Welcome page.'));
642 643

registerThemingParticipant((theme, collector) => {
644 645
	const backgroundColor = theme.getColor(welcomePageBackground);
	if (backgroundColor) {
646
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePageContainer { background-color: ${backgroundColor}; }`);
647
	}
C
Christof Marti 已提交
648 649
	const foregroundColor = theme.getColor(foreground);
	if (foregroundColor) {
650
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .caption { color: ${foregroundColor}; }`);
651
	}
C
Christof Marti 已提交
652 653
	const descriptionColor = theme.getColor(descriptionForeground);
	if (descriptionColor) {
654
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .detail { color: ${descriptionColor}; }`);
655
	}
C
Christof Marti 已提交
656 657
	const buttonColor = getExtraColor(theme, buttonBackground, { dark: 'rgba(0, 0, 0, .2)', extra_dark: 'rgba(200, 235, 255, .042)', light: 'rgba(0,0,0,.04)', hc: 'black' });
	if (buttonColor) {
658
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .commands .item button { background: ${buttonColor}; }`);
659
	}
C
Christof Marti 已提交
660 661
	const buttonHoverColor = getExtraColor(theme, buttonHoverBackground, { dark: 'rgba(200, 235, 255, .072)', extra_dark: 'rgba(200, 235, 255, .072)', light: 'rgba(0,0,0,.10)', hc: null });
	if (buttonHoverColor) {
662
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .commands .item button:hover { background: ${buttonHoverColor}; }`);
663
	}
664 665
	const link = theme.getColor(textLinkForeground);
	if (link) {
666
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage a { color: ${link}; }`);
667
	}
668 669
	const activeLink = theme.getColor(textLinkActiveForeground);
	if (activeLink) {
670 671
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage a:hover,
			.monaco-workbench .part.editor > .content .welcomePage a:active { color: ${activeLink}; }`);
672
	}
673 674
	const focusColor = theme.getColor(focusBorder);
	if (focusColor) {
675
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage a:focus { outline-color: ${focusColor}; }`);
676
	}
677 678
	const border = theme.getColor(contrastBorder);
	if (border) {
679
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .commands .item button { border-color: ${border}; }`);
680 681 682
	}
	const activeBorder = theme.getColor(activeContrastBorder);
	if (activeBorder) {
683
		collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .commands .item button:hover { outline-color: ${activeBorder}; }`);
684
	}
685
});