extensionEditor.ts 45.7 KB
Newer Older
J
Joao Moreno 已提交
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.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6
import 'vs/css!./media/extensionEditor';
J
Joao Moreno 已提交
7
import { localize } from 'vs/nls';
J
Johannes Rieken 已提交
8
import * as marked from 'vs/base/common/marked/marked';
9
import { createCancelablePromise } from 'vs/base/common/async';
J
Joao Moreno 已提交
10
import * as arrays from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
11
import { OS } from 'vs/base/common/platform';
J
Joao Moreno 已提交
12
import { Event, Emitter } from 'vs/base/common/event';
13
import { Cache, CacheResult } from 'vs/base/common/cache';
J
Joao Moreno 已提交
14
import { Action } from 'vs/base/common/actions';
15
import { isPromiseCanceledError } from 'vs/base/common/errors';
16
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
17
import { domEvent } from 'vs/base/browser/event';
18
import { append, $, addClass, removeClass, finalHandler, join, toggleClass, hide, show } from 'vs/base/browser/dom';
J
Joao Moreno 已提交
19
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
B
Benjamin Pasero 已提交
20
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
J
Joao Moreno 已提交
21
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
22
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
23 24
import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManifest, IKeyBinding, IView, IViewContainer, ExtensionType } from 'vs/platform/extensions/common/extensions';
25
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
26 27 28
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets';
J
Joao Moreno 已提交
29
import { EditorOptions } from 'vs/workbench/common/editor';
J
Joao Moreno 已提交
30
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
S
Sandeep Somavarapu 已提交
31
import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
32
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
J
Johannes Rieken 已提交
33
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
J
Joao Moreno 已提交
34
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
35
import { IOpenerService } from 'vs/platform/opener/common/opener';
S
Sandeep Somavarapu 已提交
36
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
37
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
38
import { IThemeService } from 'vs/platform/theme/common/themeService';
S
Sandeep Somavarapu 已提交
39
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
40
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
41
import { Command } from 'vs/editor/browser/editorExtensions';
42
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
43
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
44
import { Color } from 'vs/base/common/color';
45
import { assign } from 'vs/base/common/objects';
46
import { INotificationService } from 'vs/platform/notification/common/notification';
47
import { CancellationToken } from 'vs/base/common/cancellation';
48 49
import { ExtensionsTree, IExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
import { ShowCurrentReleaseNotesAction } from 'vs/workbench/contrib/update/electron-browser/update';
J
Joao Moreno 已提交
50
import { KeybindingParser } from 'vs/base/common/keybindingParser';
B
Benjamin Pasero 已提交
51
import { IStorageService } from 'vs/platform/storage/common/storage';
S
Sandeep Somavarapu 已提交
52
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
S
Sandeep Somavarapu 已提交
53 54
import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry';
import { isUndefined } from 'vs/base/common/types';
55
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
56

57 58
function renderBody(body: string): string {
	const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-core-resource://');
J
Joao Moreno 已提交
59 60 61 62
	return `<!DOCTYPE html>
		<html>
			<head>
				<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
63 64
				<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https: data:; media-src https:; script-src 'none'; style-src vscode-core-resource:; child-src 'none'; frame-src 'none';">
				<link rel="stylesheet" type="text/css" href="${styleSheetPath}">
J
Joao Moreno 已提交
65
			</head>
66 67 68 69
			<body>
				<a id="scroll-to-top" role="button" aria-label="scroll to top" href="#"><span class="icon"></span></a>
				${body}
			</body>
J
Joao Moreno 已提交
70 71
		</html>`;
}
J
Joao Moreno 已提交
72

73 74 75 76 77
function removeEmbeddedSVGs(documentContent: string): string {
	const newDocument = new DOMParser().parseFromString(documentContent, 'text/html');

	// remove all inline svgs
	const allSVGs = newDocument.documentElement.querySelectorAll('svg');
78 79 80 81 82 83 84
	if (allSVGs) {
		for (let i = 0; i < allSVGs.length; i++) {
			const svg = allSVGs[i];
			if (svg.parentNode) {
				svg.parentNode.removeChild(allSVGs[i]);
			}
		}
85 86
	}

K
kieferrm 已提交
87
	return newDocument.documentElement.outerHTML;
88 89
}

J
Joao Moreno 已提交
90 91
class NavBar {

S
Sandeep Somavarapu 已提交
92 93
	private _onChange = new Emitter<{ id: string, focus: boolean }>();
	get onChange(): Event<{ id: string, focus: boolean }> { return this._onChange.event; }
J
Joao Moreno 已提交
94

95
	private currentId: string | null = null;
J
Joao Moreno 已提交
96 97 98 99 100 101 102 103 104
	private actions: Action[];
	private actionbar: ActionBar;

	constructor(container: HTMLElement) {
		const element = append(container, $('.navbar'));
		this.actions = [];
		this.actionbar = new ActionBar(element, { animated: false });
	}

I
InspectorDeno 已提交
105
	push(id: string, label: string, tooltip: string): void {
106
		const action = new Action(id, label, undefined, true, () => this._update(id, true));
J
Joao Moreno 已提交
107

108
		action.tooltip = tooltip;
I
InspectorDeno 已提交
109

J
Joao Moreno 已提交
110 111 112 113
		this.actions.push(action);
		this.actionbar.push(action);

		if (this.actions.length === 1) {
S
Sandeep Somavarapu 已提交
114
			this._update(id);
J
Joao Moreno 已提交
115 116 117 118 119 120 121 122
		}
	}

	clear(): void {
		this.actions = dispose(this.actions);
		this.actionbar.clear();
	}

J
Joao Moreno 已提交
123 124 125 126
	update(): void {
		this._update(this.currentId);
	}

127
	_update(id: string | null = this.currentId, focus?: boolean): Promise<void> {
J
Joao Moreno 已提交
128
		this.currentId = id;
129
		this._onChange.fire({ id, focus: !!focus });
J
Joao Moreno 已提交
130
		this.actions.forEach(a => a.enabled = a.id !== id);
R
Rob Lourens 已提交
131
		return Promise.resolve(undefined);
J
Joao Moreno 已提交
132 133
	}

J
Joao Moreno 已提交
134 135 136 137 138 139 140
	dispose(): void {
		this.actionbar = dispose(this.actionbar);
	}
}

const NavbarSection = {
	Readme: 'readme',
X
XVincentX 已提交
141
	Contributions: 'contributions',
S
Sandeep Somavarapu 已提交
142
	Changelog: 'changelog',
143 144
	Dependencies: 'dependencies',
	ExtensionPack: 'extensionPack'
J
Joao Moreno 已提交
145 146
};

J
Joao Moreno 已提交
147 148 149 150
interface ILayoutParticipant {
	layout(): void;
}

S
Sandeep Somavarapu 已提交
151 152 153 154
interface IActiveElement {
	focus(): void;
}

J
Joao Moreno 已提交
155 156
export class ExtensionEditor extends BaseEditor {

M
Matt Bierner 已提交
157
	static readonly ID: string = 'workbench.editor.extension';
J
Joao Moreno 已提交
158

159
	private iconContainer: HTMLElement;
160
	private icon: HTMLImageElement;
J
Joao Moreno 已提交
161
	private name: HTMLElement;
S
Sandeep Somavarapu 已提交
162
	private identifier: HTMLElement;
163
	private preview: HTMLElement;
S
Sandeep Somavarapu 已提交
164
	private builtin: HTMLElement;
J
Joao Moreno 已提交
165 166
	private license: HTMLElement;
	private publisher: HTMLElement;
J
Joao Moreno 已提交
167
	private installCount: HTMLElement;
J
Joao Moreno 已提交
168
	private rating: HTMLElement;
169
	private repository: HTMLElement;
J
Joao Moreno 已提交
170
	private description: HTMLElement;
J
Joao Moreno 已提交
171 172 173
	private extensionActionBar: ActionBar;
	private navbar: NavBar;
	private content: HTMLElement;
174 175
	private subtextContainer: HTMLElement;
	private subtext: HTMLElement;
176
	private ignoreActionbar: ActionBar;
177
	private header: HTMLElement;
J
Joao Moreno 已提交
178

179 180 181 182
	private extensionReadme: Cache<string> | null;
	private extensionChangelog: Cache<string> | null;
	private extensionManifest: Cache<IExtensionManifest | null> | null;
	private extensionDependencies: Cache<IExtensionDependencies | null> | null;
J
Joao Moreno 已提交
183

J
Joao Moreno 已提交
184
	private layoutParticipants: ILayoutParticipant[] = [];
J
Joao Moreno 已提交
185 186
	private contentDisposables: IDisposable[] = [];
	private transientDisposables: IDisposable[] = [];
J
Joao Moreno 已提交
187
	private disposables: IDisposable[];
188
	private activeElement: IActiveElement | null;
189
	private editorLoadComplete: boolean = false;
J
Joao Moreno 已提交
190 191 192

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
M
Matt Bierner 已提交
193 194 195
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IViewletService private readonly viewletService: IViewletService,
		@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
196
		@IThemeService protected themeService: IThemeService,
M
Matt Bierner 已提交
197 198 199 200 201
		@IKeybindingService private readonly keybindingService: IKeybindingService,
		@INotificationService private readonly notificationService: INotificationService,
		@IOpenerService private readonly openerService: IOpenerService,
		@IPartService private readonly partService: IPartService,
		@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
S
Sandeep Somavarapu 已提交
202
		@IStorageService storageService: IStorageService,
203 204 205
		@IExtensionService private readonly extensionService: IExtensionService,
		@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService

J
Joao Moreno 已提交
206
	) {
207
		super(ExtensionEditor.ID, telemetryService, themeService, storageService);
J
Joao Moreno 已提交
208
		this.disposables = [];
J
Joao Moreno 已提交
209
		this.extensionReadme = null;
X
XVincentX 已提交
210
		this.extensionChangelog = null;
J
Joao Moreno 已提交
211
		this.extensionManifest = null;
S
Sandeep Somavarapu 已提交
212
		this.extensionDependencies = null;
J
Joao Moreno 已提交
213 214
	}

215 216
	createEditor(parent: HTMLElement): void {
		const root = append(parent, $('.extension-editor'));
217
		this.header = append(root, $('.header'));
J
Joao Moreno 已提交
218

219 220
		this.iconContainer = append(this.header, $('.icon-container'));
		this.icon = append(this.iconContainer, $<HTMLImageElement>('img.icon', { draggable: false }));
J
Joao Moreno 已提交
221

222
		const details = append(this.header, $('.details'));
J
Joao Moreno 已提交
223
		const title = append(details, $('.title'));
J
Joao Moreno 已提交
224
		this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") }));
J
Joao Moreno 已提交
225
		this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") }));
S
Sandeep Somavarapu 已提交
226

227
		this.preview = append(title, $('span.preview', { title: localize('preview', "Preview") }));
S
Sandeep Somavarapu 已提交
228 229 230 231
		this.preview.textContent = localize('preview', "Preview");

		this.builtin = append(title, $('span.builtin'));
		this.builtin.textContent = localize('builtin', "Built-in");
J
Joao Moreno 已提交
232 233

		const subtitle = append(details, $('.subtitle'));
J
Joao Moreno 已提交
234
		this.publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name") }));
J
Joao Moreno 已提交
235

J
Joao Moreno 已提交
236
		this.installCount = append(subtitle, $('span.install', { title: localize('install count', "Install count") }));
J
Joao Moreno 已提交
237

J
Joao Moreno 已提交
238
		this.rating = append(subtitle, $('span.rating.clickable', { title: localize('rating', "Rating") }));
J
Joao Moreno 已提交
239

240 241 242 243
		this.repository = append(subtitle, $('span.repository.clickable'));
		this.repository.textContent = localize('repository', 'Repository');
		this.repository.style.display = 'none';

J
Joao Moreno 已提交
244
		this.license = append(subtitle, $('span.license.clickable'));
J
Joao Moreno 已提交
245
		this.license.textContent = localize('license', 'License');
246
		this.license.style.display = 'none';
J
Joao Moreno 已提交
247

J
Joao Moreno 已提交
248 249
		this.description = append(details, $('.description'));

J
Joao Moreno 已提交
250
		const extensionActions = append(details, $('.actions'));
251 252 253
		this.extensionActionBar = new ActionBar(extensionActions, {
			animated: false,
			actionItemProvider: (action: Action) => {
S
Sandeep Somavarapu 已提交
254 255
				if (action instanceof ExtensionEditorDropDownAction) {
					return action.createActionItem();
256 257 258 259
				}
				return null;
			}
		});
J
Joao Moreno 已提交
260

261 262 263
		this.subtextContainer = append(details, $('.subtext-container'));
		this.subtext = append(this.subtextContainer, $('.subtext'));
		this.ignoreActionbar = new ActionBar(this.subtextContainer, { animated: false });
264 265 266

		this.disposables.push(this.extensionActionBar);
		this.disposables.push(this.ignoreActionbar);
267

J
Joao Moreno 已提交
268
		Event.chain(this.extensionActionBar.onDidRun)
J
Joao Moreno 已提交
269 270 271
			.map(({ error }) => error)
			.filter(error => !!error)
			.on(this.onError, this, this.disposables);
272

J
Joao Moreno 已提交
273
		Event.chain(this.ignoreActionbar.onDidRun)
274 275 276 277
			.map(({ error }) => error)
			.filter(error => !!error)
			.on(this.onError, this, this.disposables);

J
Joao Moreno 已提交
278 279
		const body = append(root, $('.body'));
		this.navbar = new NavBar(body);
J
Joao Moreno 已提交
280

J
Joao Moreno 已提交
281
		this.content = append(body, $('.content'));
J
Joao Moreno 已提交
282 283
	}

284 285 286 287
	async setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Promise<void> {
		const runningExtensions = await this.extensionService.getExtensions();
		const colorThemes = await this.workbenchThemeService.getColorThemes();
		const fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
288

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
		this.activeElement = null;
		this.editorLoadComplete = false;
		const extension = input.extension;

		this.transientDisposables = dispose(this.transientDisposables);

		this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(token)));
		this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token)));
		this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token)));
		this.extensionDependencies = new Cache(() => createCancelablePromise(token => this.extensionsWorkbenchService.loadDependencies(extension, token)));

		const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer);
		const onError = Event.once(domEvent(this.icon, 'error'));
		onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables);
		this.icon.src = extension.iconUrl;

		this.name.textContent = extension.displayName;
		this.identifier.textContent = extension.identifier.id;
		this.preview.style.display = extension.preview ? 'inherit' : 'none';
		this.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none';

		this.publisher.textContent = extension.publisherDisplayName;
		this.description.textContent = extension.description;

		const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
		let recommendationsData = {};
		if (extRecommendations[extension.identifier.id.toLowerCase()]) {
			recommendationsData = { recommendationReason: extRecommendations[extension.identifier.id.toLowerCase()].reasonId };
		}

		/* __GDPR__
		"extensionGallery:openExtension" : {
			"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
			"${include}": [
				"${GalleryExtensionTelemetryData}"
			]
		}
		*/
		this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData));

		toggleClass(this.name, 'clickable', !!extension.url);
		toggleClass(this.publisher, 'clickable', !!extension.url);
		toggleClass(this.rating, 'clickable', !!extension.url);
		if (extension.url) {
			this.name.onclick = finalHandler(() => window.open(extension.url));
			this.rating.onclick = finalHandler(() => window.open(`${extension.url}#review-details`));
			this.publisher.onclick = finalHandler(() => {
				this.viewletService.openViewlet(VIEWLET_ID, true)
					.then(viewlet => viewlet as IExtensionsViewlet)
					.then(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`));
			});

			if (extension.licenseUrl) {
				this.license.onclick = finalHandler(() => window.open(extension.licenseUrl));
				this.license.style.display = 'initial';
			} else {
				this.license.onclick = null;
				this.license.style.display = 'none';
			}
		} else {
			this.name.onclick = null;
			this.rating.onclick = null;
			this.publisher.onclick = null;
			this.license.onclick = null;
			this.license.style.display = 'none';
		}

		if (extension.repository) {
			this.repository.onclick = finalHandler(() => window.open(extension.repository));
			this.repository.style.display = 'initial';
		}
		else {
			this.repository.onclick = null;
			this.repository.style.display = 'none';
		}
J
Joao Moreno 已提交
364

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
		const widgets = [
			remoteBadge,
			this.instantiationService.createInstance(InstallCountWidget, this.installCount, false),
			this.instantiationService.createInstance(RatingsWidget, this.rating, false)
		];
		const reloadAction = this.instantiationService.createInstance(ReloadAction);
		const actions = [
			reloadAction,
			this.instantiationService.createInstance(StatusLabelAction),
			this.instantiationService.createInstance(UpdateAction),
			this.instantiationService.createInstance(SetColorThemeAction, colorThemes),
			this.instantiationService.createInstance(SetFileIconThemeAction, fileIconThemes),
			this.instantiationService.createInstance(EnableDropDownAction),
			this.instantiationService.createInstance(DisableDropDownAction, runningExtensions),
			this.instantiationService.createInstance(CombinedInstallAction),
			this.instantiationService.createInstance(MaliciousStatusLabelAction, true),
		];
		const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]);
		extensionContainers.extension = extension;

		this.extensionActionBar.clear();
		this.extensionActionBar.push(actions, { icon: true, label: true });
		this.transientDisposables.push(...[...actions, ...widgets, extensionContainers]);

		this.setSubText(extension, reloadAction);
		this.content.innerHTML = ''; // Clear content before setting navbar actions.

		this.navbar.clear();
		this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);

		if (extension.hasReadme()) {
			this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file"));
		}
		this.extensionManifest.get()
			.promise
			.then(manifest => {
				if (extension.extensionPack.length) {
					this.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together"));
S
Sandeep Somavarapu 已提交
403
				}
404 405
				if (manifest && manifest.contributes) {
					this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension"));
S
Sandeep Somavarapu 已提交
406
				}
407 408 409 410 411
				if (extension.hasChangelog()) {
					this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file"));
				}
				if (extension.dependencies.length) {
					this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on"));
S
Sandeep Somavarapu 已提交
412
				}
413
				this.editorLoadComplete = true;
S
Sandeep Somavarapu 已提交
414
			});
415 416

		return super.setInput(input, options, token);
J
Joao Moreno 已提交
417
	}
418

419
	private setSubText(extension: IExtension, reloadAction: ReloadAction): void {
420 421 422 423 424 425 426 427 428 429 430 431 432 433
		hide(this.subtextContainer);

		const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction);
		const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction);
		ignoreAction.extension = extension;
		undoIgnoreAction.extension = extension;
		ignoreAction.enabled = false;
		undoIgnoreAction.enabled = false;

		this.ignoreActionbar.clear();
		this.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true });
		this.transientDisposables.push(ignoreAction, undoIgnoreAction);

		const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
S
Sandeep Somavarapu 已提交
434
		if (extRecommendations[extension.identifier.id.toLowerCase()]) {
435
			ignoreAction.enabled = true;
S
Sandeep Somavarapu 已提交
436
			this.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
437
			show(this.subtextContainer);
S
Sandeep Somavarapu 已提交
438
		} else if (this.extensionTipsService.getAllIgnoredRecommendations().global.indexOf(extension.identifier.id.toLowerCase()) !== -1) {
439 440 441 442 443 444 445 446 447
			undoIgnoreAction.enabled = true;
			this.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
			show(this.subtextContainer);
		}
		else {
			this.subtext.textContent = '';
		}

		this.extensionTipsService.onRecommendationChange(change => {
S
Sandeep Somavarapu 已提交
448
			if (change.extensionId.toLowerCase() === extension.identifier.id.toLowerCase()) {
449 450 451
				if (change.isRecommended) {
					undoIgnoreAction.enabled = false;
					const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
S
Sandeep Somavarapu 已提交
452
					if (extRecommendations[extension.identifier.id.toLowerCase()]) {
453
						ignoreAction.enabled = true;
S
Sandeep Somavarapu 已提交
454
						this.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
455 456 457 458 459 460 461 462 463
					}
				} else {
					undoIgnoreAction.enabled = true;
					ignoreAction.enabled = false;
					this.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
				}
			}
		});

464
		this.transientDisposables.push(reloadAction.onDidChange(e => {
465
			if (e.tooltip) {
466 467 468 469 470
				this.subtext.textContent = reloadAction.tooltip;
				show(this.subtextContainer);
				ignoreAction.enabled = false;
				undoIgnoreAction.enabled = false;
			}
471 472 473 474 475 476 477
			if (e.enabled === true) {
				show(this.subtextContainer);
			}
			if (e.enabled === false) {
				hide(this.subtextContainer);
			}
		}));
478 479
	}

S
Sandeep Somavarapu 已提交
480 481 482 483 484 485
	focus(): void {
		if (this.activeElement) {
			this.activeElement.focus();
		}
	}

486
	showFind(): void {
S
Sandeep Somavarapu 已提交
487 488
		if (this.activeElement instanceof WebviewElement) {
			this.activeElement.showFind();
489 490 491
		}
	}

S
Sandeep Somavarapu 已提交
492
	private onNavbarChange(extension: IExtension, { id, focus }: { id: string, focus: boolean }): void {
493 494 495
		if (this.editorLoadComplete) {
			/* __GDPR__
				"extensionEditor:navbarChange" : {
496
					"navItem": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
497 498 499 500 501 502 503 504
					"${include}": [
						"${GalleryExtensionTelemetryData}"
					]
				}
			*/
			this.telemetryService.publicLog('extensionEditor:navbarChange', assign(extension.telemetryData, { navItem: id }));
		}

S
Sandeep Somavarapu 已提交
505 506
		this.contentDisposables = dispose(this.contentDisposables);
		this.content.innerHTML = '';
S
Sandeep Somavarapu 已提交
507 508 509 510 511 512 513 514 515 516
		this.activeElement = null;
		this.open(id, extension)
			.then(activeElement => {
				this.activeElement = activeElement;
				if (focus) {
					this.focus();
				}
			});
	}

517
	private open(id: string, extension: IExtension): Promise<IActiveElement | null> {
J
Joao Moreno 已提交
518
		switch (id) {
519 520 521
			case NavbarSection.Readme: return this.openReadme();
			case NavbarSection.Contributions: return this.openContributions();
			case NavbarSection.Changelog: return this.openChangelog();
522
			case NavbarSection.Dependencies: return this.openDependencies(extension);
523
			case NavbarSection.ExtensionPack: return this.openExtensionPack(extension);
J
Joao Moreno 已提交
524
		}
S
Sandeep Somavarapu 已提交
525
		return Promise.resolve(null);
J
Joao Moreno 已提交
526
	}
J
Joao Moreno 已提交
527

S
Sandeep Somavarapu 已提交
528 529
	private openMarkdown(cacheResult: CacheResult<string>, noContentCopy: string): Promise<IActiveElement> {
		return this.loadContents(() => cacheResult)
J
Joao Moreno 已提交
530
			.then(marked.parse)
531
			.then(renderBody)
532
			.then(removeEmbeddedSVGs)
S
Sandeep Somavarapu 已提交
533
			.then(body => {
534 535 536 537 538 539
				const wbeviewElement = this.instantiationService.createInstance(WebviewElement,
					this.partService.getContainer(Parts.EDITOR_PART),
					{},
					{
						svgWhiteList: this.extensionsWorkbenchService.allowedBadgeProviders
					});
S
Sandeep Somavarapu 已提交
540 541
				wbeviewElement.mountTo(this.content);
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, wbeviewElement);
542
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));
S
Sandeep Somavarapu 已提交
543
				wbeviewElement.contents = body;
J
Joao Moreno 已提交
544

S
Sandeep Somavarapu 已提交
545
				wbeviewElement.onDidClickLink(link => {
546 547 548
					if (!link) {
						return;
					}
M
Matt Bierner 已提交
549
					// Whitelist supported schemes for links
550
					if (['http', 'https', 'mailto'].indexOf(link.scheme) >= 0 || (link.scheme === 'command' && link.path === ShowCurrentReleaseNotesAction.ID)) {
M
Matt Bierner 已提交
551 552 553
						this.openerService.open(link);
					}
				}, null, this.contentDisposables);
S
Sandeep Somavarapu 已提交
554 555
				this.contentDisposables.push(wbeviewElement);
				return wbeviewElement;
J
Joao Moreno 已提交
556
			})
R
Rob Lourens 已提交
557
			.then(undefined, () => {
558
				const p = append(this.content, $('p.nocontent'));
X
XVincentX 已提交
559
				p.textContent = noContentCopy;
S
Sandeep Somavarapu 已提交
560
				return p;
561
			});
J
Joao Moreno 已提交
562
	}
J
Joao Moreno 已提交
563

S
Sandeep Somavarapu 已提交
564 565
	private openReadme(): Promise<IActiveElement> {
		return this.openMarkdown(this.extensionReadme.get(), localize('noReadme', "No README available."));
566 567
	}

S
Sandeep Somavarapu 已提交
568 569
	private openChangelog(): Promise<IActiveElement> {
		return this.openMarkdown(this.extensionChangelog.get(), localize('noChangelog', "No Changelog available."));
570 571
	}

S
Sandeep Somavarapu 已提交
572
	private openContributions(): Promise<IActiveElement> {
S
Sandeep Somavarapu 已提交
573
		const content = $('div', { class: 'subcontent', tabindex: '0' });
S
Sandeep Somavarapu 已提交
574
		return this.loadContents(() => this.extensionManifest.get())
575
			.then(manifest => {
576
				const scrollableContent = new DomScrollableElement(content, {});
J
Joao Moreno 已提交
577 578 579 580 581

				const layout = () => scrollableContent.scanDomNode();
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));

J
Joao Moreno 已提交
582
				const renders = [
B
Benjamin Pasero 已提交
583
					this.renderSettings(content, manifest, layout),
J
Joao Moreno 已提交
584
					this.renderCommands(content, manifest, layout),
B
Benjamin Pasero 已提交
585
					this.renderLanguages(content, manifest, layout),
586 587
					this.renderColorThemes(content, manifest, layout),
					this.renderIconThemes(content, manifest, layout),
588
					this.renderColors(content, manifest, layout),
B
Benjamin Pasero 已提交
589
					this.renderJSONValidation(content, manifest, layout),
590
					this.renderDebuggers(content, manifest, layout),
S
Sandeep Somavarapu 已提交
591
					this.renderViewContainers(content, manifest, layout),
592 593
					this.renderViews(content, manifest, layout),
					this.renderLocalizations(content, manifest, layout)
J
Joao Moreno 已提交
594 595 596
				];

				const isEmpty = !renders.reduce((v, r) => r || v, false);
J
Joao Moreno 已提交
597
				scrollableContent.scanDomNode();
598 599

				if (isEmpty) {
S
Sandeep Somavarapu 已提交
600 601
					append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
					append(this.content, content);
602 603 604 605
				} else {
					append(this.content, scrollableContent.getDomNode());
					this.contentDisposables.push(scrollableContent);
				}
S
Sandeep Somavarapu 已提交
606
				return content;
S
Sandeep Somavarapu 已提交
607
			}, () => {
S
Sandeep Somavarapu 已提交
608 609
				append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
				append(this.content, content);
S
Sandeep Somavarapu 已提交
610
				return content;
611
			});
J
Joao Moreno 已提交
612 613
	}

S
Sandeep Somavarapu 已提交
614
	private openDependencies(extension: IExtension): Promise<IActiveElement> {
615 616
		if (extension.dependencies.length === 0) {
			append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
S
Sandeep Somavarapu 已提交
617
			return Promise.resolve(this.content);
618
		}
S
Sandeep Somavarapu 已提交
619

S
Sandeep Somavarapu 已提交
620
		return this.loadContents(() => this.extensionDependencies.get())
J
Johannes Rieken 已提交
621
			.then<IActiveElement, IActiveElement>(extensionDependencies => {
622 623 624 625 626
				if (extensionDependencies) {
					const content = $('div', { class: 'subcontent' });
					const scrollableContent = new DomScrollableElement(content, {});
					append(this.content, scrollableContent.getDomNode());
					this.contentDisposables.push(scrollableContent);
627

628 629 630 631 632 633 634 635
					const dependenciesTree = this.renderDependencies(content, extensionDependencies);
					const layout = () => {
						scrollableContent.scanDomNode();
						const scrollDimensions = scrollableContent.getScrollDimensions();
						dependenciesTree.layout(scrollDimensions.height);
					};
					const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
					this.contentDisposables.push(toDisposable(removeLayoutParticipant));
S
Sandeep Somavarapu 已提交
636

637 638 639 640 641 642 643
					this.contentDisposables.push(dependenciesTree);
					scrollableContent.scanDomNode();
					return { focus() { dependenciesTree.domFocus(); } };
				} else {
					append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
					return Promise.resolve(this.content);
				}
644 645
			}, error => {
				append(this.content, $('p.nocontent')).textContent = error;
646
				this.notificationService.error(error);
S
Sandeep Somavarapu 已提交
647
				return this.content;
648
			});
S
Sandeep Somavarapu 已提交
649 650
	}

651
	private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): Tree {
652 653 654
		class ExtensionData implements IExtensionData {

			private readonly extensionDependencies: IExtensionDependencies;
B
Benjamin Pasero 已提交
655

656 657 658 659 660 661 662
			constructor(extensionDependencies: IExtensionDependencies) {
				this.extensionDependencies = extensionDependencies;
			}

			get extension(): IExtension {
				return this.extensionDependencies.extension;
			}
663

664
			get parent(): IExtensionData | null {
665
				return this.extensionDependencies.dependent ? new ExtensionData(this.extensionDependencies.dependent) : null;
666 667
			}

668 669 670 671
			get hasChildren(): boolean {
				return this.extensionDependencies.hasDependencies;
			}

672 673
			getChildren(): Promise<IExtensionData[] | null> {
				return this.extensionDependencies.dependencies ? Promise.resolve(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : Promise.resolve(null);
674 675 676 677 678 679
			}
		}

		return this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extensionDependencies), container);
	}

S
Sandeep Somavarapu 已提交
680
	private openExtensionPack(extension: IExtension): Promise<IActiveElement> {
681 682 683 684
		const content = $('div', { class: 'subcontent' });
		const scrollableContent = new DomScrollableElement(content, {});
		append(this.content, scrollableContent.getDomNode());
		this.contentDisposables.push(scrollableContent);
685

S
Sandeep Somavarapu 已提交
686
		const extensionsPackTree = this.renderExtensionPack(content, extension);
687
		const layout = () => {
688
			scrollableContent.scanDomNode();
689
			const scrollDimensions = scrollableContent.getScrollDimensions();
S
Sandeep Somavarapu 已提交
690
			extensionsPackTree.layout(scrollDimensions.height);
691 692 693 694
		};
		const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
		this.contentDisposables.push(toDisposable(removeLayoutParticipant));

S
Sandeep Somavarapu 已提交
695
		this.contentDisposables.push(extensionsPackTree);
696
		scrollableContent.scanDomNode();
S
Sandeep Somavarapu 已提交
697
		return Promise.resolve({ focus() { extensionsPackTree.domFocus(); } });
698 699 700 701 702 703 704
	}

	private renderExtensionPack(container: HTMLElement, extension: IExtension): Tree {
		const extensionsWorkbenchService = this.extensionsWorkbenchService;
		class ExtensionData implements IExtensionData {

			readonly extension: IExtension;
705
			readonly parent: IExtensionData | null;
706 707 708

			constructor(extension: IExtension, parent?: IExtensionData) {
				this.extension = extension;
709
				this.parent = parent || null;
710 711 712 713 714 715
			}

			get hasChildren(): boolean {
				return this.extension.extensionPack.length > 0;
			}

716
			getChildren(): Promise<IExtensionData[] | null> {
717 718 719 720 721
				if (this.hasChildren) {
					const names = arrays.distinct(this.extension.extensionPack, e => e.toLowerCase());
					return extensionsWorkbenchService.queryGallery({ names, pageSize: names.length })
						.then(result => result.firstPage.map(extension => new ExtensionData(extension, this)));
				}
S
Sandeep Somavarapu 已提交
722
				return Promise.resolve(null);
723 724 725 726
			}
		}

		return this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension), container);
S
Sandeep Somavarapu 已提交
727 728
	}

B
Benjamin Pasero 已提交
729
	private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
730 731
		const contributes = manifest.contributes;
		const configuration = contributes && contributes.configuration;
732 733 734 735 736 737 738 739
		let properties = {};
		if (Array.isArray(configuration)) {
			configuration.forEach(config => {
				properties = { ...properties, ...config.properties };
			});
		} else if (configuration) {
			properties = configuration.properties;
		}
J
Joao Moreno 已提交
740
		const contrib = properties ? Object.keys(properties) : [];
J
Joao Moreno 已提交
741

J
Joao Moreno 已提交
742
		if (!contrib.length) {
743
			return false;
J
Joao Moreno 已提交
744 745
		}

J
Joao Moreno 已提交
746
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
747 748 749 750 751 752
			$('summary', undefined, localize('settings', "Settings ({0})", contrib.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('setting name', "Name")),
					$('th', undefined, localize('description', "Description")),
					$('th', undefined, localize('default', "Default"))
J
Joao Moreno 已提交
753
				),
754 755 756 757
				...contrib.map(key => $('tr', undefined,
					$('td', undefined, $('code', undefined, key)),
					$('td', undefined, properties[key].description),
					$('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`))
J
Joao Moreno 已提交
758
				))
J
Joao Moreno 已提交
759
			)
J
Joao Moreno 已提交
760 761 762
		);

		append(container, details);
763
		return true;
J
Joao Moreno 已提交
764 765
	}

B
Benjamin Pasero 已提交
766
	private renderDebuggers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
767 768
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.debuggers || [];
J
Joao Moreno 已提交
769

J
Joao Moreno 已提交
770
		if (!contrib.length) {
771
			return false;
J
Joao Moreno 已提交
772 773
		}

J
Joao Moreno 已提交
774
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
775 776 777 778 779
			$('summary', undefined, localize('debuggers', "Debuggers ({0})", contrib.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('debugger name', "Name")),
					$('th', undefined, localize('debugger type', "Type")),
780
				),
781 782 783
				...contrib.map(d => $('tr', undefined,
					$('td', undefined, d.label!),
					$('td', undefined, d.type)))
J
Joao Moreno 已提交
784
			)
J
Joao Moreno 已提交
785 786 787
		);

		append(container, details);
788
		return true;
J
Joao Moreno 已提交
789 790
	}

S
Sandeep Somavarapu 已提交
791 792 793 794
	private renderViewContainers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.viewsContainers || {};

795
		let viewContainers = Object.keys(contrib).reduce((result, location) => {
S
Sandeep Somavarapu 已提交
796 797 798
			let viewContainersForLocation: IViewContainer[] = contrib[location];
			result.push(...viewContainersForLocation.map(viewContainer => ({ ...viewContainer, location })));
			return result;
799
		}, [] as Array<{ id: string, title: string, location: string }>);
S
Sandeep Somavarapu 已提交
800 801 802 803 804 805

		if (!viewContainers.length) {
			return false;
		}

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
806 807 808 809
			$('summary', undefined, localize('viewContainers', "View Containers ({0})", viewContainers.length)),
			$('table', undefined,
				$('tr', undefined, $('th', undefined, localize('view container id', "ID")), $('th', undefined, localize('view container title', "Title")), $('th', undefined, localize('view container location', "Where"))),
				...viewContainers.map(viewContainer => $('tr', undefined, $('td', undefined, viewContainer.id), $('td', undefined, viewContainer.title), $('td', undefined, viewContainer.location)))
S
Sandeep Somavarapu 已提交
810 811 812 813 814 815 816
			)
		);

		append(container, details);
		return true;
	}

817 818 819 820
	private renderViews(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.views || {};

821
		let views = Object.keys(contrib).reduce((result, location) => {
822 823 824
			let viewsForLocation: IView[] = contrib[location];
			result.push(...viewsForLocation.map(view => ({ ...view, location })));
			return result;
825
		}, [] as Array<{ id: string, name: string, location: string }>);
826 827 828 829 830 831

		if (!views.length) {
			return false;
		}

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
832 833 834 835
			$('summary', undefined, localize('views', "Views ({0})", views.length)),
			$('table', undefined,
				$('tr', undefined, $('th', undefined, localize('view id', "ID")), $('th', undefined, localize('view name', "Name")), $('th', undefined, localize('view location', "Where"))),
				...views.map(view => $('tr', undefined, $('td', undefined, view.id), $('td', undefined, view.name), $('td', undefined, view.location)))
836 837 838 839 840 841 842
			)
		);

		append(container, details);
		return true;
	}

843 844 845 846 847 848 849 850 851
	private renderLocalizations(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const localizations = contributes && contributes.localizations || [];

		if (!localizations.length) {
			return false;
		}

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
852 853 854 855
			$('summary', undefined, localize('localizations', "Localizations ({0})", localizations.length)),
			$('table', undefined,
				$('tr', undefined, $('th', undefined, localize('localizations language id', "Language Id")), $('th', undefined, localize('localizations language name', "Language Name")), $('th', undefined, localize('localizations localized language name', "Language Name (Localized)"))),
				...localizations.map(localization => $('tr', undefined, $('td', undefined, localization.languageId), $('td', undefined, localization.languageName || ''), $('td', undefined, localization.localizedLanguageName || '')))
856 857 858 859 860 861 862
			)
		);

		append(container, details);
		return true;
	}

863
	private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
864 865
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.themes || [];
J
Joao Moreno 已提交
866

J
Joao Moreno 已提交
867
		if (!contrib.length) {
868
			return false;
J
Joao Moreno 已提交
869 870
		}

J
Joao Moreno 已提交
871
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
872 873
			$('summary', undefined, localize('colorThemes', "Color Themes ({0})", contrib.length)),
			$('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label)))
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888
		);

		append(container, details);
		return true;
	}

	private renderIconThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.iconThemes || [];

		if (!contrib.length) {
			return false;
		}

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
889 890
			$('summary', undefined, localize('iconThemes', "Icon Themes ({0})", contrib.length)),
			$('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label)))
J
Joao Moreno 已提交
891 892 893
		);

		append(container, details);
894
		return true;
J
Joao Moreno 已提交
895 896
	}

897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
	private renderColors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const colors = contributes && contributes.colors;

		if (!colors || !colors.length) {
			return false;
		}

		function colorPreview(colorReference: string): Node[] {
			let result: Node[] = [];
			if (colorReference && colorReference[0] === '#') {
				let color = Color.fromHex(colorReference);
				if (color) {
					result.push($('span', { class: 'colorBox', style: 'background-color: ' + Color.Format.CSS.format(color) }, ''));
				}
			}
913
			result.push($('code', undefined, colorReference));
914 915 916 917
			return result;
		}

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
918 919 920 921 922 923 924 925
			$('summary', undefined, localize('colors', "Colors ({0})", colors.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('colorId', "Id")),
					$('th', undefined, localize('description', "Description")),
					$('th', undefined, localize('defaultDark', "Dark Default")),
					$('th', undefined, localize('defaultLight', "Light Default")),
					$('th', undefined, localize('defaultHC', "High Contrast Default"))
926
				),
927 928 929 930 931 932
				...colors.map(color => $('tr', undefined,
					$('td', undefined, $('code', undefined, color.id)),
					$('td', undefined, color.description),
					$('td', undefined, ...colorPreview(color.defaults.dark)),
					$('td', undefined, ...colorPreview(color.defaults.light)),
					$('td', undefined, ...colorPreview(color.defaults.highContrast))
933 934 935 936 937 938 939 940 941
				))
			)
		);

		append(container, details);
		return true;
	}


B
Benjamin Pasero 已提交
942
	private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
943 944
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
945 946

		if (!contrib.length) {
947
			return false;
J
Joao Moreno 已提交
948 949
		}

J
Joao Moreno 已提交
950
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
951 952 953 954 955
			$('summary', undefined, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('fileMatch', "File Match")),
					$('th', undefined, localize('schema', "Schema"))
S
Sandeep Somavarapu 已提交
956
				),
957 958 959
				...contrib.map(v => $('tr', undefined,
					$('td', undefined, $('code', undefined, v.fileMatch)),
					$('td', undefined, v.url)
S
Sandeep Somavarapu 已提交
960
				))));
J
Joao Moreno 已提交
961 962

		append(container, details);
963
		return true;
J
Joao Moreno 已提交
964 965
	}

966
	private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
967 968
		const contributes = manifest.contributes;
		const rawCommands = contributes && contributes.commands || [];
J
Joao Moreno 已提交
969
		const commands = rawCommands.map(c => ({
970 971
			id: c.command,
			title: c.title,
972 973
			keybindings: [] as ResolvedKeybinding[],
			menus: [] as string[]
974 975
		}));

J
Joao Moreno 已提交
976
		const byId = arrays.index(commands, c => c.id);
977

J
Joao Moreno 已提交
978
		const menus = contributes && contributes.menus || {};
J
Joao Moreno 已提交
979

980 981
		Object.keys(menus).forEach(context => {
			menus[context].forEach(menu => {
J
Joao Moreno 已提交
982
				let command = byId[menu.command];
983 984

				if (!command) {
J
Joao Moreno 已提交
985
					command = { id: menu.command, title: '', keybindings: [], menus: [context] };
J
Joao Moreno 已提交
986
					byId[command.id] = command;
987 988 989 990 991 992 993
					commands.push(command);
				} else {
					command.menus.push(context);
				}
			});
		});

S
Sandeep Somavarapu 已提交
994
		const rawKeybindings = contributes && contributes.keybindings ? (Array.isArray(contributes.keybindings) ? contributes.keybindings : [contributes.keybindings]) : [];
J
Joao Moreno 已提交
995

996
		rawKeybindings.forEach(rawKeybinding => {
S
Sandeep Somavarapu 已提交
997
			const keybinding = this.resolveKeybinding(rawKeybinding);
J
Joao Moreno 已提交
998

S
Sandeep Somavarapu 已提交
999
			if (!keybinding) {
J
Joao Moreno 已提交
1000 1001 1002
				return;
			}

J
Joao Moreno 已提交
1003
			let command = byId[rawKeybinding.command];
J
Joao Moreno 已提交
1004 1005

			if (!command) {
S
Sandeep Somavarapu 已提交
1006
				command = { id: rawKeybinding.command, title: '', keybindings: [keybinding], menus: [] };
J
Joao Moreno 已提交
1007
				byId[command.id] = command;
J
Joao Moreno 已提交
1008 1009
				commands.push(command);
			} else {
S
Sandeep Somavarapu 已提交
1010
				command.keybindings.push(keybinding);
J
Joao Moreno 已提交
1011 1012 1013
			}
		});

1014
		if (!commands.length) {
1015
			return false;
1016 1017
		}

S
Sandeep Somavarapu 已提交
1018 1019
		const renderKeybinding = (keybinding: ResolvedKeybinding): HTMLElement => {
			const element = $('');
1020
			new KeybindingLabel(element, OS).set(keybinding);
S
Sandeep Somavarapu 已提交
1021 1022 1023
			return element;
		};

J
Joao Moreno 已提交
1024
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
1025 1026 1027 1028 1029 1030 1031
			$('summary', undefined, localize('commands', "Commands ({0})", commands.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('command name', "Name")),
					$('th', undefined, localize('description', "Description")),
					$('th', undefined, localize('keyboard shortcuts', "Keyboard Shortcuts")),
					$('th', undefined, localize('menuContexts', "Menu Contexts"))
1032
				),
1033 1034 1035 1036 1037
				...commands.map(c => $('tr', undefined,
					$('td', undefined, $('code', undefined, c.id)),
					$('td', undefined, c.title),
					$('td', undefined, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))),
					$('td', undefined, ...c.menus.map(context => $('code', undefined, context)))
1038 1039
				))
			)
J
Joao Moreno 已提交
1040 1041 1042
		);

		append(container, details);
1043
		return true;
1044 1045
	}

B
Benjamin Pasero 已提交
1046
	private renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
1047 1048
		const contributes = manifest.contributes;
		const rawLanguages = contributes && contributes.languages || [];
J
Joao Moreno 已提交
1049 1050
		const languages = rawLanguages.map(l => ({
			id: l.id,
J
Joao Moreno 已提交
1051
			name: (l.aliases || [])[0] || l.id,
J
Joao Moreno 已提交
1052 1053 1054
			extensions: l.extensions || [],
			hasGrammar: false,
			hasSnippets: false
J
Joao Moreno 已提交
1055 1056
		}));

J
Joao Moreno 已提交
1057 1058
		const byId = arrays.index(languages, l => l.id);

J
Joao Moreno 已提交
1059
		const grammars = contributes && contributes.grammars || [];
J
Joao Moreno 已提交
1060 1061 1062 1063 1064

		grammars.forEach(grammar => {
			let language = byId[grammar.language];

			if (!language) {
J
Joao Moreno 已提交
1065
				language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false };
J
Joao Moreno 已提交
1066 1067 1068 1069 1070 1071 1072
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasGrammar = true;
			}
		});

J
Joao Moreno 已提交
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
		const snippets = contributes && contributes.snippets || [];

		snippets.forEach(snippet => {
			let language = byId[snippet.language];

			if (!language) {
				language = { id: snippet.language, name: snippet.language, extensions: [], hasGrammar: false, hasSnippets: true };
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasSnippets = true;
			}
		});

J
Joao Moreno 已提交
1087
		if (!languages.length) {
1088
			return false;
J
Joao Moreno 已提交
1089 1090
		}

J
Joao Moreno 已提交
1091
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
1092 1093 1094 1095 1096 1097 1098 1099
			$('summary', undefined, localize('languages', "Languages ({0})", languages.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('language id', "ID")),
					$('th', undefined, localize('language name', "Name")),
					$('th', undefined, localize('file extensions', "File Extensions")),
					$('th', undefined, localize('grammar', "Grammar")),
					$('th', undefined, localize('snippets', "Snippets"))
J
Joao Moreno 已提交
1100
				),
1101 1102 1103 1104 1105 1106
				...languages.map(l => $('tr', undefined,
					$('td', undefined, l.id),
					$('td', undefined, l.name),
					$('td', undefined, ...join(l.extensions.map(ext => $('code', undefined, ext)), ' ')),
					$('td', undefined, document.createTextNode(l.hasGrammar ? '✔︎' : '')),
					$('td', undefined, document.createTextNode(l.hasSnippets ? '✔︎' : ''))
J
Joao Moreno 已提交
1107 1108
				))
			)
J
Joao Moreno 已提交
1109 1110 1111
		);

		append(container, details);
1112
		return true;
J
Joao Moreno 已提交
1113 1114
	}

1115 1116
	private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | null {
		let key: string | undefined;
1117

J
Johannes Rieken 已提交
1118
		switch (process.platform) {
1119 1120 1121 1122 1123
			case 'win32': key = rawKeyBinding.win; break;
			case 'linux': key = rawKeyBinding.linux; break;
			case 'darwin': key = rawKeyBinding.mac; break;
		}

J
Joao Moreno 已提交
1124
		const keyBinding = KeybindingParser.parseKeybinding(key || rawKeyBinding.key, OS);
A
Alex Dima 已提交
1125 1126 1127 1128
		if (!keyBinding) {
			return null;
		}

S
Sandeep Somavarapu 已提交
1129
		return this.keybindingService.resolveKeybinding(keyBinding)[0];
1130 1131
	}

S
Sandeep Somavarapu 已提交
1132
	private loadContents<T>(loadingTask: () => CacheResult<T>): Promise<T> {
J
Joao Moreno 已提交
1133
		addClass(this.content, 'loading');
J
Joao Moreno 已提交
1134

1135 1136 1137 1138 1139
		const result = loadingTask();
		const onDone = () => removeClass(this.content, 'loading');
		result.promise.then(onDone, onDone);

		this.contentDisposables.push(toDisposable(() => result.dispose()));
J
Joao Moreno 已提交
1140

1141
		return result.promise;
J
Joao Moreno 已提交
1142 1143 1144
	}

	layout(): void {
J
Joao Moreno 已提交
1145
		this.layoutParticipants.forEach(p => p.layout());
J
Joao Moreno 已提交
1146 1147
	}

1148 1149 1150 1151 1152
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

1153
		this.notificationService.error(err);
1154 1155
	}

J
Joao Moreno 已提交
1156
	dispose(): void {
J
Joao Moreno 已提交
1157 1158
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
1159 1160 1161
		super.dispose();
	}
}
1162

1163 1164 1165 1166 1167 1168 1169 1170
class ShowExtensionEditorFindCommand extends Command {
	public runCommand(accessor: ServicesAccessor, args: any): void {
		const extensionEditor = this.getExtensionEditor(accessor);
		if (extensionEditor) {
			extensionEditor.showFind();
		}
	}

1171
	private getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor | null {
1172
		const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor;
B
Benjamin Pasero 已提交
1173 1174
		if (activeControl instanceof ExtensionEditor) {
			return activeControl;
1175 1176 1177 1178 1179 1180
		}
		return null;
	}
}
const showCommand = new ShowExtensionEditorFindCommand({
	id: 'editor.action.extensioneditor.showfind',
1181
	precondition: ContextKeyExpr.equals('activeEditor', ExtensionEditor.ID),
1182
	kbOpts: {
A
Alex Dima 已提交
1183
		primary: KeyMod.CtrlCmd | KeyCode.KEY_F,
1184
		weight: KeybindingWeight.EditorContrib
1185 1186
	}
});
A
Alex Dima 已提交
1187
showCommand.register();