extensionEditor.ts 43.2 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';
M
Matt Bierner 已提交
12
import { Event, Emitter, once, chain } 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';
S
Sandeep Somavarapu 已提交
18
import { append, $, addClass, removeClass, finalHandler, join, toggleClass } 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';
S
Sandeep Somavarapu 已提交
23
import { IExtensionManifest, IKeyBinding, IView, IExtensionTipsService, LocalExtensionType, IViewContainer } from 'vs/platform/extensionManagement/common/extensionManagement';
24
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
25
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
26
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies } from 'vs/workbench/parts/extensions/common/extensions';
27
import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
J
Joao Moreno 已提交
28
import { EditorOptions } from 'vs/workbench/common/editor';
J
Joao Moreno 已提交
29
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
30
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, MaliciousStatusLabelAction, DisabledStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
M
Matt Bierner 已提交
31
import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement';
J
Johannes Rieken 已提交
32
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
J
Joao Moreno 已提交
33
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
34
import { IOpenerService } from 'vs/platform/opener/common/opener';
S
Sandeep Somavarapu 已提交
35
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
36
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
37
import { IThemeService } from 'vs/platform/theme/common/themeService';
S
Sandeep Somavarapu 已提交
38
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
39
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
40
import { Command } from 'vs/editor/browser/editorExtensions';
41
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
42
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
43
import { Color } from 'vs/base/common/color';
44
import { assign } from 'vs/base/common/objects';
45
import { INotificationService } from 'vs/platform/notification/common/notification';
46
import { CancellationToken } from 'vs/base/common/cancellation';
47
import { ExtensionsTree, IExtensionData } from 'vs/workbench/parts/extensions/browser/extensionsViewer';
48
import { ShowCurrentReleaseNotesAction } from 'vs/workbench/parts/update/electron-browser/update';
J
Joao Moreno 已提交
49
import { KeybindingParser } from 'vs/base/common/keybindingParser';
50
import { IStorageService } from 'vs/platform/storage2/common/storage2';
51

52 53
function renderBody(body: string): string {
	const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-core-resource://');
J
Joao Moreno 已提交
54 55 56 57
	return `<!DOCTYPE html>
		<html>
			<head>
				<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
58 59
				<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 已提交
60
			</head>
61 62 63 64
			<body>
				<a id="scroll-to-top" role="button" aria-label="scroll to top" href="#"><span class="icon"></span></a>
				${body}
			</body>
J
Joao Moreno 已提交
65 66
		</html>`;
}
J
Joao Moreno 已提交
67

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

	// remove all inline svgs
	const allSVGs = newDocument.documentElement.querySelectorAll('svg');
	for (let i = 0; i < allSVGs.length; i++) {
		allSVGs[i].parentNode.removeChild(allSVGs[i]);
	}

K
kieferrm 已提交
77
	return newDocument.documentElement.outerHTML;
78 79
}

J
Joao Moreno 已提交
80 81
class NavBar {

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

85
	private currentId: string | null = null;
J
Joao Moreno 已提交
86 87 88 89 90 91 92 93 94
	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 已提交
95
	push(id: string, label: string, tooltip: string): void {
S
Sandeep Somavarapu 已提交
96
		const action = new Action(id, label, null, true, () => this._update(id, true));
J
Joao Moreno 已提交
97

98
		action.tooltip = tooltip;
I
InspectorDeno 已提交
99

J
Joao Moreno 已提交
100 101 102 103
		this.actions.push(action);
		this.actionbar.push(action);

		if (this.actions.length === 1) {
S
Sandeep Somavarapu 已提交
104
			this._update(id);
J
Joao Moreno 已提交
105 106 107 108 109 110 111 112
		}
	}

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

J
Joao Moreno 已提交
113 114 115 116
	update(): void {
		this._update(this.currentId);
	}

S
Sandeep Somavarapu 已提交
117
	_update(id: string = this.currentId, focus?: boolean): Promise<void> {
J
Joao Moreno 已提交
118
		this.currentId = id;
S
Sandeep Somavarapu 已提交
119
		this._onChange.fire({ id, focus });
J
Joao Moreno 已提交
120
		this.actions.forEach(a => a.enabled = a.id !== id);
S
Sandeep Somavarapu 已提交
121
		return Promise.resolve(null);
J
Joao Moreno 已提交
122 123
	}

J
Joao Moreno 已提交
124 125 126 127 128 129 130
	dispose(): void {
		this.actionbar = dispose(this.actionbar);
	}
}

const NavbarSection = {
	Readme: 'readme',
X
XVincentX 已提交
131
	Contributions: 'contributions',
S
Sandeep Somavarapu 已提交
132
	Changelog: 'changelog',
133 134
	Dependencies: 'dependencies',
	ExtensionPack: 'extensionPack'
J
Joao Moreno 已提交
135 136
};

J
Joao Moreno 已提交
137 138 139 140
interface ILayoutParticipant {
	layout(): void;
}

S
Sandeep Somavarapu 已提交
141 142 143 144
interface IActiveElement {
	focus(): void;
}

J
Joao Moreno 已提交
145 146
export class ExtensionEditor extends BaseEditor {

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

149
	private icon: HTMLImageElement;
J
Joao Moreno 已提交
150
	private name: HTMLElement;
S
Sandeep Somavarapu 已提交
151
	private identifier: HTMLElement;
152
	private preview: HTMLElement;
S
Sandeep Somavarapu 已提交
153
	private builtin: HTMLElement;
J
Joao Moreno 已提交
154 155
	private license: HTMLElement;
	private publisher: HTMLElement;
J
Joao Moreno 已提交
156
	private installCount: HTMLElement;
J
Joao Moreno 已提交
157
	private rating: HTMLElement;
158
	private repository: HTMLElement;
J
Joao Moreno 已提交
159
	private description: HTMLElement;
J
Joao Moreno 已提交
160 161 162
	private extensionActionBar: ActionBar;
	private navbar: NavBar;
	private content: HTMLElement;
163
	private recommendation: HTMLElement;
164
	private recommendationText: HTMLElement;
165
	private ignoreActionbar: ActionBar;
166
	private header: HTMLElement;
J
Joao Moreno 已提交
167

J
Joao Moreno 已提交
168
	private extensionReadme: Cache<string>;
X
XVincentX 已提交
169
	private extensionChangelog: Cache<string>;
J
Joao Moreno 已提交
170
	private extensionManifest: Cache<IExtensionManifest>;
S
Sandeep Somavarapu 已提交
171
	private extensionDependencies: Cache<IExtensionDependencies>;
J
Joao Moreno 已提交
172

J
Joao Moreno 已提交
173
	private layoutParticipants: ILayoutParticipant[] = [];
J
Joao Moreno 已提交
174 175
	private contentDisposables: IDisposable[] = [];
	private transientDisposables: IDisposable[] = [];
J
Joao Moreno 已提交
176
	private disposables: IDisposable[];
S
Sandeep Somavarapu 已提交
177
	private activeElement: IActiveElement;
178
	private editorLoadComplete: boolean = false;
J
Joao Moreno 已提交
179 180 181

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
M
Matt Bierner 已提交
182 183 184
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IViewletService private readonly viewletService: IViewletService,
		@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
185
		@IThemeService protected themeService: IThemeService,
M
Matt Bierner 已提交
186 187 188 189 190
		@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,
191
		@IStorageService storageService: IStorageService
J
Joao Moreno 已提交
192
	) {
193
		super(ExtensionEditor.ID, telemetryService, themeService, storageService);
J
Joao Moreno 已提交
194
		this.disposables = [];
J
Joao Moreno 已提交
195
		this.extensionReadme = null;
X
XVincentX 已提交
196
		this.extensionChangelog = null;
J
Joao Moreno 已提交
197
		this.extensionManifest = null;
S
Sandeep Somavarapu 已提交
198
		this.extensionDependencies = null;
J
Joao Moreno 已提交
199 200
	}

201 202
	createEditor(parent: HTMLElement): void {
		const root = append(parent, $('.extension-editor'));
203
		this.header = append(root, $('.header'));
J
Joao Moreno 已提交
204

205
		this.icon = append(this.header, $<HTMLImageElement>('img.icon', { draggable: false }));
J
Joao Moreno 已提交
206

207
		const details = append(this.header, $('.details'));
J
Joao Moreno 已提交
208
		const title = append(details, $('.title'));
J
Joao Moreno 已提交
209
		this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") }));
J
Joao Moreno 已提交
210
		this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") }));
S
Sandeep Somavarapu 已提交
211

212
		this.preview = append(title, $('span.preview', { title: localize('preview', "Preview") }));
S
Sandeep Somavarapu 已提交
213 214 215 216
		this.preview.textContent = localize('preview', "Preview");

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

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

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

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

225 226 227 228
		this.repository = append(subtitle, $('span.repository.clickable'));
		this.repository.textContent = localize('repository', 'Repository');
		this.repository.style.display = 'none';

J
Joao Moreno 已提交
229
		this.license = append(subtitle, $('span.license.clickable'));
J
Joao Moreno 已提交
230
		this.license.textContent = localize('license', 'License');
231
		this.license.style.display = 'none';
J
Joao Moreno 已提交
232

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

J
Joao Moreno 已提交
235
		const extensionActions = append(details, $('.actions'));
236 237 238 239 240 241 242 243 244 245 246 247
		this.extensionActionBar = new ActionBar(extensionActions, {
			animated: false,
			actionItemProvider: (action: Action) => {
				if (action.id === EnableAction.ID) {
					return (<EnableAction>action).actionItem;
				}
				if (action.id === DisableAction.ID) {
					return (<DisableAction>action).actionItem;
				}
				return null;
			}
		});
J
Joao Moreno 已提交
248

249
		this.recommendation = append(details, $('.recommendation'));
250 251 252 253 254
		this.recommendationText = append(this.recommendation, $('.recommendation-text'));
		this.ignoreActionbar = new ActionBar(this.recommendation, { animated: false });

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

I
isidor 已提交
256
		chain(this.extensionActionBar.onDidRun)
J
Joao Moreno 已提交
257 258 259
			.map(({ error }) => error)
			.filter(error => !!error)
			.on(this.onError, this, this.disposables);
260

261 262 263 264 265
		chain(this.ignoreActionbar.onDidRun)
			.map(({ error }) => error)
			.filter(error => !!error)
			.on(this.onError, this, this.disposables);

J
Joao Moreno 已提交
266 267
		const body = append(root, $('.body'));
		this.navbar = new NavBar(body);
J
Joao Moreno 已提交
268

J
Joao Moreno 已提交
269
		this.content = append(body, $('.content'));
J
Joao Moreno 已提交
270 271
	}

272
	setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
S
Sandeep Somavarapu 已提交
273
		this.activeElement = null;
274
		this.editorLoadComplete = false;
J
Joao Moreno 已提交
275 276
		const extension = input.extension;

J
Joao Moreno 已提交
277 278
		this.transientDisposables = dispose(this.transientDisposables);

279 280 281 282
		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)));
J
Joao Moreno 已提交
283

284 285 286 287
		const onError = once(domEvent(this.icon, 'error'));
		onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables);
		this.icon.src = extension.iconUrl;

J
Joao Moreno 已提交
288
		this.name.textContent = extension.displayName;
S
Sandeep Somavarapu 已提交
289
		this.identifier.textContent = extension.id;
S
Sandeep Somavarapu 已提交
290 291
		this.preview.style.display = extension.preview ? 'inherit' : 'none';
		this.builtin.style.display = extension.type === LocalExtensionType.System ? 'inherit' : 'none';
292

J
Joao Moreno 已提交
293 294
		this.publisher.textContent = extension.publisherDisplayName;
		this.description.textContent = extension.description;
J
Joao Moreno 已提交
295

296
		removeClass(this.header, 'recommendation-ignored');
297 298
		removeClass(this.header, 'recommended');

299
		const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
300
		let recommendationsData = {};
301 302
		if (extRecommendations[extension.id.toLowerCase()]) {
			addClass(this.header, 'recommended');
303
			this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText;
304
			recommendationsData = { recommendationReason: extRecommendations[extension.id.toLowerCase()].reasonId };
305 306 307 308 309
		} else if (this.extensionTipsService.getAllIgnoredRecommendations().global.indexOf(extension.id.toLowerCase()) !== -1) {
			addClass(this.header, 'recommendation-ignored');
			this.recommendationText.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
		}
		else {
310
			this.recommendationText.textContent = '';
311 312
		}

313
		/* __GDPR__
314 315 316 317 318 319
		"extensionGallery:openExtension" : {
			"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
			"${include}": [
				"${GalleryExtensionTelemetryData}"
			]
		}
320 321 322
		*/
		this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData));

S
Sandeep Somavarapu 已提交
323 324 325
		toggleClass(this.name, 'clickable', !!extension.url);
		toggleClass(this.publisher, 'clickable', !!extension.url);
		toggleClass(this.rating, 'clickable', !!extension.url);
326 327 328
		if (extension.url) {
			this.name.onclick = finalHandler(() => window.open(extension.url));
			this.rating.onclick = finalHandler(() => window.open(`${extension.url}#review-details`));
J
Joao Moreno 已提交
329
			this.publisher.onclick = finalHandler(() => {
330
				this.viewletService.openViewlet(VIEWLET_ID, true)
J
Joao Moreno 已提交
331
					.then(viewlet => viewlet as IExtensionsViewlet)
332
					.then(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`));
J
Joao Moreno 已提交
333
			});
334 335

			if (extension.licenseUrl) {
B
Benjamin Pasero 已提交
336
				this.license.onclick = finalHandler(() => window.open(extension.licenseUrl));
337 338 339 340 341
				this.license.style.display = 'initial';
			} else {
				this.license.onclick = null;
				this.license.style.display = 'none';
			}
S
Sandeep Somavarapu 已提交
342 343 344 345
		} else {
			this.name.onclick = null;
			this.rating.onclick = null;
			this.publisher.onclick = null;
S
Sandeep Somavarapu 已提交
346 347
			this.license.onclick = null;
			this.license.style.display = 'none';
J
Joao Moreno 已提交
348 349
		}

350 351 352 353 354 355 356 357 358
		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';
		}

359
		const install = this.instantiationService.createInstance(InstallCountWidget, this.installCount, { extension });
J
Joao Moreno 已提交
360 361
		this.transientDisposables.push(install);

J
Joao Moreno 已提交
362
		const ratings = this.instantiationService.createInstance(RatingsWidget, this.rating, { extension });
J
Joao Moreno 已提交
363
		this.transientDisposables.push(ratings);
J
Joao Moreno 已提交
364

J
Joao Moreno 已提交
365
		const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, true);
S
Sandeep Somavarapu 已提交
366
		const disabledStatusAction = this.instantiationService.createInstance(DisabledStatusLabelAction);
367
		const installAction = this.instantiationService.createInstance(CombinedInstallAction);
368
		const updateAction = this.instantiationService.createInstance(UpdateAction);
J
Joao Moreno 已提交
369
		const enableAction = this.instantiationService.createInstance(EnableAction);
370
		const disableAction = this.instantiationService.createInstance(DisableAction);
371
		const reloadAction = this.instantiationService.createInstance(ReloadAction, true);
J
Joao Moreno 已提交
372 373

		installAction.extension = extension;
J
Joao Moreno 已提交
374
		maliciousStatusAction.extension = extension;
S
Sandeep Somavarapu 已提交
375
		disabledStatusAction.extension = extension;
J
Joao Moreno 已提交
376 377
		updateAction.extension = extension;
		enableAction.extension = extension;
378
		disableAction.extension = extension;
379
		reloadAction.extension = extension;
J
Joao Moreno 已提交
380

J
Joao Moreno 已提交
381
		this.extensionActionBar.clear();
S
Sandeep Somavarapu 已提交
382 383
		this.extensionActionBar.push([disabledStatusAction, reloadAction, updateAction, enableAction, disableAction, installAction, maliciousStatusAction], { icon: true, label: true });
		this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, maliciousStatusAction, disabledStatusAction);
J
Joao Moreno 已提交
384

385
		const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction);
386
		const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction);
387
		ignoreAction.extension = extension;
388
		undoIgnoreAction.extension = extension;
389 390

		this.extensionTipsService.onRecommendationChange(change => {
391 392
			if (change.extensionId.toLowerCase() === extension.id.toLowerCase()) {
				if (change.isRecommended) {
393
					removeClass(this.header, 'recommendation-ignored');
394 395 396 397 398 399 400 401 402 403
					const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
					if (extRecommendations[extension.id.toLowerCase()]) {
						addClass(this.header, 'recommended');
						this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText;
					}
				} else {
					addClass(this.header, 'recommendation-ignored');
					removeClass(this.header, 'recommended');
					this.recommendationText.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
				}
404 405 406 407
			}
		});

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

S
Sandeep Somavarapu 已提交
411
		this.content.innerHTML = ''; // Clear content before setting navbar actions.
S
Sandeep Somavarapu 已提交
412

J
Joao Moreno 已提交
413 414
		this.navbar.clear();
		this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
S
Sandeep Somavarapu 已提交
415 416 417

		if (extension.hasReadme()) {
			this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file"));
418
		}
S
Sandeep Somavarapu 已提交
419
		this.extensionManifest.get()
420
			.promise
S
Sandeep Somavarapu 已提交
421 422 423 424
			.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 已提交
425
				if (manifest && manifest.contributes) {
S
Sandeep Somavarapu 已提交
426 427 428 429 430 431 432 433 434 435
					this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension"));
				}
				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"));
				}
				this.editorLoadComplete = true;
			});
J
Joao Moreno 已提交
436

437
		return super.setInput(input, options, token);
J
Joao Moreno 已提交
438
	}
439

S
Sandeep Somavarapu 已提交
440 441 442 443 444 445
	focus(): void {
		if (this.activeElement) {
			this.activeElement.focus();
		}
	}

446
	showFind(): void {
S
Sandeep Somavarapu 已提交
447 448
		if (this.activeElement instanceof WebviewElement) {
			this.activeElement.showFind();
449 450 451
		}
	}

S
Sandeep Somavarapu 已提交
452
	private onNavbarChange(extension: IExtension, { id, focus }: { id: string, focus: boolean }): void {
453 454 455
		if (this.editorLoadComplete) {
			/* __GDPR__
				"extensionEditor:navbarChange" : {
456
					"navItem": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
457 458 459 460 461 462 463 464
					"${include}": [
						"${GalleryExtensionTelemetryData}"
					]
				}
			*/
			this.telemetryService.publicLog('extensionEditor:navbarChange', assign(extension.telemetryData, { navItem: id }));
		}

S
Sandeep Somavarapu 已提交
465 466
		this.contentDisposables = dispose(this.contentDisposables);
		this.content.innerHTML = '';
S
Sandeep Somavarapu 已提交
467 468 469 470 471 472 473 474 475 476 477
		this.activeElement = null;
		this.open(id, extension)
			.then(activeElement => {
				this.activeElement = activeElement;
				if (focus) {
					this.focus();
				}
			});
	}

	private open(id: string, extension: IExtension): Promise<IActiveElement> {
J
Joao Moreno 已提交
478
		switch (id) {
479 480 481
			case NavbarSection.Readme: return this.openReadme();
			case NavbarSection.Contributions: return this.openContributions();
			case NavbarSection.Changelog: return this.openChangelog();
482
			case NavbarSection.Dependencies: return this.openDependencies(extension);
483
			case NavbarSection.ExtensionPack: return this.openExtensionPack(extension);
J
Joao Moreno 已提交
484
		}
S
Sandeep Somavarapu 已提交
485
		return Promise.resolve(null);
J
Joao Moreno 已提交
486
	}
J
Joao Moreno 已提交
487

S
Sandeep Somavarapu 已提交
488 489
	private openMarkdown(cacheResult: CacheResult<string>, noContentCopy: string): Promise<IActiveElement> {
		return this.loadContents(() => cacheResult)
J
Joao Moreno 已提交
490
			.then(marked.parse)
491
			.then(renderBody)
492
			.then(removeEmbeddedSVGs)
S
Sandeep Somavarapu 已提交
493
			.then(body => {
494
				const allowedBadgeProviders = this.extensionsWorkbenchService.allowedBadgeProviders;
495
				const webViewOptions = allowedBadgeProviders.length > 0 ? { allowScripts: false, allowSvgs: false, svgWhiteList: allowedBadgeProviders } : {};
S
Sandeep Somavarapu 已提交
496 497 498
				const wbeviewElement = this.instantiationService.createInstance(WebviewElement, this.partService.getContainer(Parts.EDITOR_PART), webViewOptions);
				wbeviewElement.mountTo(this.content);
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, wbeviewElement);
499
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));
S
Sandeep Somavarapu 已提交
500
				wbeviewElement.contents = body;
J
Joao Moreno 已提交
501

S
Sandeep Somavarapu 已提交
502
				wbeviewElement.onDidClickLink(link => {
503 504 505
					if (!link) {
						return;
					}
M
Matt Bierner 已提交
506
					// Whitelist supported schemes for links
507
					if (['http', 'https', 'mailto'].indexOf(link.scheme) >= 0 || (link.scheme === 'command' && link.path === ShowCurrentReleaseNotesAction.ID)) {
M
Matt Bierner 已提交
508 509 510
						this.openerService.open(link);
					}
				}, null, this.contentDisposables);
S
Sandeep Somavarapu 已提交
511 512
				this.contentDisposables.push(wbeviewElement);
				return wbeviewElement;
J
Joao Moreno 已提交
513
			})
J
Joao Moreno 已提交
514
			.then(null, () => {
515
				const p = append(this.content, $('p.nocontent'));
X
XVincentX 已提交
516
				p.textContent = noContentCopy;
S
Sandeep Somavarapu 已提交
517
				return p;
518
			});
J
Joao Moreno 已提交
519
	}
J
Joao Moreno 已提交
520

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

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

S
Sandeep Somavarapu 已提交
529
	private openContributions(): Promise<IActiveElement> {
S
Sandeep Somavarapu 已提交
530
		const content = $('div', { class: 'subcontent', tabindex: '0' });
S
Sandeep Somavarapu 已提交
531
		return this.loadContents(() => this.extensionManifest.get())
532
			.then(manifest => {
533
				const scrollableContent = new DomScrollableElement(content, {});
J
Joao Moreno 已提交
534 535 536 537 538

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

J
Joao Moreno 已提交
539
				const renders = [
B
Benjamin Pasero 已提交
540
					this.renderSettings(content, manifest, layout),
J
Joao Moreno 已提交
541
					this.renderCommands(content, manifest, layout),
B
Benjamin Pasero 已提交
542
					this.renderLanguages(content, manifest, layout),
543 544
					this.renderColorThemes(content, manifest, layout),
					this.renderIconThemes(content, manifest, layout),
545
					this.renderColors(content, manifest, layout),
B
Benjamin Pasero 已提交
546
					this.renderJSONValidation(content, manifest, layout),
547
					this.renderDebuggers(content, manifest, layout),
S
Sandeep Somavarapu 已提交
548
					this.renderViewContainers(content, manifest, layout),
549 550
					this.renderViews(content, manifest, layout),
					this.renderLocalizations(content, manifest, layout)
J
Joao Moreno 已提交
551 552 553
				];

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

				if (isEmpty) {
S
Sandeep Somavarapu 已提交
557 558
					append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
					append(this.content, content);
559 560 561 562
				} else {
					append(this.content, scrollableContent.getDomNode());
					this.contentDisposables.push(scrollableContent);
				}
S
Sandeep Somavarapu 已提交
563
				return content;
S
Sandeep Somavarapu 已提交
564
			}, () => {
S
Sandeep Somavarapu 已提交
565 566
				append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
				append(this.content, content);
S
Sandeep Somavarapu 已提交
567
				return content;
568
			});
J
Joao Moreno 已提交
569 570
	}

S
Sandeep Somavarapu 已提交
571
	private openDependencies(extension: IExtension): Promise<IActiveElement> {
572 573
		if (extension.dependencies.length === 0) {
			append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
S
Sandeep Somavarapu 已提交
574
			return Promise.resolve(this.content);
575
		}
S
Sandeep Somavarapu 已提交
576

S
Sandeep Somavarapu 已提交
577
		return this.loadContents(() => this.extensionDependencies.get())
578
			.then(extensionDependencies => {
579
				const content = $('div', { class: 'subcontent' });
580
				const scrollableContent = new DomScrollableElement(content, {});
581 582 583
				append(this.content, scrollableContent.getDomNode());
				this.contentDisposables.push(scrollableContent);

584
				const dependenciesTree = this.renderDependencies(content, extensionDependencies);
585 586
				const layout = () => {
					scrollableContent.scanDomNode();
587
					const scrollDimensions = scrollableContent.getScrollDimensions();
588
					dependenciesTree.layout(scrollDimensions.height);
589 590 591
				};
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));
S
Sandeep Somavarapu 已提交
592

593
				this.contentDisposables.push(dependenciesTree);
594
				scrollableContent.scanDomNode();
S
Sandeep Somavarapu 已提交
595
				return { focus() { dependenciesTree.domFocus(); } };
596 597
			}, error => {
				append(this.content, $('p.nocontent')).textContent = error;
598
				this.notificationService.error(error);
S
Sandeep Somavarapu 已提交
599
				return this.content;
600
			});
S
Sandeep Somavarapu 已提交
601 602
	}

603
	private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): Tree {
604 605 606
		class ExtensionData implements IExtensionData {

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

608 609 610 611 612 613 614
			constructor(extensionDependencies: IExtensionDependencies) {
				this.extensionDependencies = extensionDependencies;
			}

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

616 617
			get parent(): IExtensionData {
				return this.extensionDependencies.dependent ? new ExtensionData(this.extensionDependencies.dependent) : null;
618 619
			}

620 621 622 623 624
			get hasChildren(): boolean {
				return this.extensionDependencies.hasDependencies;
			}

			getChildren(): Promise<IExtensionData[]> {
S
Sandeep Somavarapu 已提交
625
				return this.extensionDependencies.dependencies ? Promise.resolve(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : null;
626 627 628 629 630 631
			}
		}

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

S
Sandeep Somavarapu 已提交
632
	private openExtensionPack(extension: IExtension): Promise<IActiveElement> {
633 634 635 636
		const content = $('div', { class: 'subcontent' });
		const scrollableContent = new DomScrollableElement(content, {});
		append(this.content, scrollableContent.getDomNode());
		this.contentDisposables.push(scrollableContent);
637

S
Sandeep Somavarapu 已提交
638
		const extensionsPackTree = this.renderExtensionPack(content, extension);
639
		const layout = () => {
640
			scrollableContent.scanDomNode();
641
			const scrollDimensions = scrollableContent.getScrollDimensions();
S
Sandeep Somavarapu 已提交
642
			extensionsPackTree.layout(scrollDimensions.height);
643 644 645 646
		};
		const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
		this.contentDisposables.push(toDisposable(removeLayoutParticipant));

S
Sandeep Somavarapu 已提交
647
		this.contentDisposables.push(extensionsPackTree);
648
		scrollableContent.scanDomNode();
S
Sandeep Somavarapu 已提交
649
		return Promise.resolve({ focus() { extensionsPackTree.domFocus(); } });
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
	}

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

			readonly extension: IExtension;
			readonly parent: IExtensionData;

			constructor(extension: IExtension, parent?: IExtensionData) {
				this.extension = extension;
				this.parent = parent;
			}

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

			getChildren(): Promise<IExtensionData[]> {
				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 已提交
674
				return Promise.resolve(null);
675 676 677 678
			}
		}

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

B
Benjamin Pasero 已提交
681
	private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
682 683
		const contributes = manifest.contributes;
		const configuration = contributes && contributes.configuration;
684 685 686 687 688 689 690 691
		let properties = {};
		if (Array.isArray(configuration)) {
			configuration.forEach(config => {
				properties = { ...properties, ...config.properties };
			});
		} else if (configuration) {
			properties = configuration.properties;
		}
J
Joao Moreno 已提交
692
		const contrib = properties ? Object.keys(properties) : [];
J
Joao Moreno 已提交
693

J
Joao Moreno 已提交
694
		if (!contrib.length) {
695
			return false;
J
Joao Moreno 已提交
696 697
		}

J
Joao Moreno 已提交
698
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
699
			$('summary', null, localize('settings', "Settings ({0})", contrib.length)),
J
Joao Moreno 已提交
700
			$('table', null,
J
Joao Moreno 已提交
701 702 703
				$('tr', null,
					$('th', null, localize('setting name', "Name")),
					$('th', null, localize('description', "Description")),
J
nls  
Joao Moreno 已提交
704
					$('th', null, localize('default', "Default"))
J
Joao Moreno 已提交
705 706 707 708 709 710
				),
				...contrib.map(key => $('tr', null,
					$('td', null, $('code', null, key)),
					$('td', null, properties[key].description),
					$('td', null, $('code', null, properties[key].default))
				))
J
Joao Moreno 已提交
711
			)
J
Joao Moreno 已提交
712 713 714
		);

		append(container, details);
715
		return true;
J
Joao Moreno 已提交
716 717
	}

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

J
Joao Moreno 已提交
722
		if (!contrib.length) {
723
			return false;
J
Joao Moreno 已提交
724 725
		}

J
Joao Moreno 已提交
726
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
727
			$('summary', null, localize('debuggers', "Debuggers ({0})", contrib.length)),
J
Joao Moreno 已提交
728
			$('table', null,
729 730 731 732 733 734 735
				$('tr', null,
					$('th', null, localize('debugger name', "Name")),
					$('th', null, localize('debugger type', "Type")),
				),
				...contrib.map(d => $('tr', null,
					$('td', null, d.label),
					$('td', null, d.type)))
J
Joao Moreno 已提交
736
			)
J
Joao Moreno 已提交
737 738 739
		);

		append(container, details);
740
		return true;
J
Joao Moreno 已提交
741 742
	}

S
Sandeep Somavarapu 已提交
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768
	private renderViewContainers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.viewsContainers || {};

		let viewContainers = <{ id: string, title: string, location: string }[]>Object.keys(contrib).reduce((result, location) => {
			let viewContainersForLocation: IViewContainer[] = contrib[location];
			result.push(...viewContainersForLocation.map(viewContainer => ({ ...viewContainer, location })));
			return result;
		}, []);

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

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
			$('summary', null, localize('viewContainers', "View Containers ({0})", viewContainers.length)),
			$('table', null,
				$('tr', null, $('th', null, localize('view container id', "ID")), $('th', null, localize('view container title', "Title")), $('th', null, localize('view container location', "Where"))),
				...viewContainers.map(viewContainer => $('tr', null, $('td', null, viewContainer.id), $('td', null, viewContainer.title), $('td', null, viewContainer.location)))
			)
		);

		append(container, details);
		return true;
	}

769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
	private renderViews(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.views || {};

		let views = <{ id: string, name: string, location: string }[]>Object.keys(contrib).reduce((result, location) => {
			let viewsForLocation: IView[] = contrib[location];
			result.push(...viewsForLocation.map(view => ({ ...view, location })));
			return result;
		}, []);

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

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
			$('summary', null, localize('views', "Views ({0})", views.length)),
			$('table', null,
				$('tr', null, $('th', null, localize('view id', "ID")), $('th', null, localize('view name', "Name")), $('th', null, localize('view location', "Where"))),
				...views.map(view => $('tr', null, $('td', null, view.id), $('td', null, view.name), $('td', null, view.location)))
			)
		);

		append(container, details);
		return true;
	}

795 796 797 798 799 800 801 802 803 804 805
	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 },
			$('summary', null, localize('localizations', "Localizations ({0})", localizations.length)),
			$('table', null,
A
Alex Dima 已提交
806
				$('tr', null, $('th', null, localize('localizations language id', "Language Id")), $('th', null, localize('localizations language name', "Language Name")), $('th', null, localize('localizations localized language name', "Language Name (Localized)"))),
807
				...localizations.map(localization => $('tr', null, $('td', null, localization.languageId), $('td', null, localization.languageName), $('td', null, localization.localizedLanguageName)))
808 809 810 811 812 813 814
			)
		);

		append(container, details);
		return true;
	}

815
	private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
816 817
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.themes || [];
J
Joao Moreno 已提交
818

J
Joao Moreno 已提交
819
		if (!contrib.length) {
820
			return false;
J
Joao Moreno 已提交
821 822
		}

J
Joao Moreno 已提交
823
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
			$('summary', null, localize('colorThemes', "Color Themes ({0})", contrib.length)),
			$('ul', null, ...contrib.map(theme => $('li', null, theme.label)))
		);

		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 },
			$('summary', null, localize('iconThemes', "Icon Themes ({0})", contrib.length)),
J
Joao Moreno 已提交
842 843 844 845
			$('ul', null, ...contrib.map(theme => $('li', null, theme.label)))
		);

		append(container, details);
846
		return true;
J
Joao Moreno 已提交
847 848
	}

849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
	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) }, ''));
				}
			}
			result.push($('code', null, colorReference));
			return result;
		}

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
			$('summary', null, localize('colors', "Colors ({0})", colors.length)),
			$('table', null,
				$('tr', null,
					$('th', null, localize('colorId', "Id")),
					$('th', null, localize('description', "Description")),
					$('th', null, localize('defaultDark', "Dark Default")),
					$('th', null, localize('defaultLight', "Light Default")),
					$('th', null, localize('defaultHC', "High Contrast Default"))
				),
				...colors.map(color => $('tr', null,
					$('td', null, $('code', null, color.id)),
					$('td', null, color.description),
					$('td', null, ...colorPreview(color.defaults.dark)),
					$('td', null, ...colorPreview(color.defaults.light)),
					$('td', null, ...colorPreview(color.defaults.highContrast))
				))
			)
		);

		append(container, details);
		return true;
	}


B
Benjamin Pasero 已提交
894
	private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
895 896
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
897 898

		if (!contrib.length) {
899
			return false;
J
Joao Moreno 已提交
900 901
		}

J
Joao Moreno 已提交
902
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
903
			$('summary', null, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
S
Sandeep Somavarapu 已提交
904 905 906 907 908 909
			$('table', null,
				$('tr', null,
					$('th', null, localize('fileMatch', "File Match")),
					$('th', null, localize('schema', "Schema"))
				),
				...contrib.map(v => $('tr', null,
S
Sandeep Somavarapu 已提交
910
					$('td', null, $('code', null, v.fileMatch)),
S
Sandeep Somavarapu 已提交
911 912
					$('td', null, v.url)
				))));
J
Joao Moreno 已提交
913 914

		append(container, details);
915
		return true;
J
Joao Moreno 已提交
916 917
	}

918
	private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
919 920
		const contributes = manifest.contributes;
		const rawCommands = contributes && contributes.commands || [];
J
Joao Moreno 已提交
921
		const commands = rawCommands.map(c => ({
922 923
			id: c.command,
			title: c.title,
J
Joao Moreno 已提交
924
			keybindings: [],
925 926 927
			menus: []
		}));

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

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

932 933
		Object.keys(menus).forEach(context => {
			menus[context].forEach(menu => {
J
Joao Moreno 已提交
934
				let command = byId[menu.command];
935 936

				if (!command) {
J
Joao Moreno 已提交
937
					command = { id: menu.command, title: '', keybindings: [], menus: [context] };
J
Joao Moreno 已提交
938
					byId[command.id] = command;
939 940 941 942 943 944 945
					commands.push(command);
				} else {
					command.menus.push(context);
				}
			});
		});

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

948
		rawKeybindings.forEach(rawKeybinding => {
S
Sandeep Somavarapu 已提交
949
			const keybinding = this.resolveKeybinding(rawKeybinding);
J
Joao Moreno 已提交
950

S
Sandeep Somavarapu 已提交
951
			if (!keybinding) {
J
Joao Moreno 已提交
952 953 954
				return;
			}

J
Joao Moreno 已提交
955
			let command = byId[rawKeybinding.command];
J
Joao Moreno 已提交
956 957

			if (!command) {
S
Sandeep Somavarapu 已提交
958
				command = { id: rawKeybinding.command, title: '', keybindings: [keybinding], menus: [] };
J
Joao Moreno 已提交
959
				byId[command.id] = command;
J
Joao Moreno 已提交
960 961
				commands.push(command);
			} else {
S
Sandeep Somavarapu 已提交
962
				command.keybindings.push(keybinding);
J
Joao Moreno 已提交
963 964 965
			}
		});

966
		if (!commands.length) {
967
			return false;
968 969
		}

S
Sandeep Somavarapu 已提交
970 971 972 973 974 975
		const renderKeybinding = (keybinding: ResolvedKeybinding): HTMLElement => {
			const element = $('');
			new KeybindingLabel(element, OS).set(keybinding, null);
			return element;
		};

J
Joao Moreno 已提交
976
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
977 978 979 980 981
			$('summary', null, localize('commands', "Commands ({0})", commands.length)),
			$('table', null,
				$('tr', null,
					$('th', null, localize('command name', "Name")),
					$('th', null, localize('description', "Description")),
J
Joao Moreno 已提交
982
					$('th', null, localize('keyboard shortcuts', "Keyboard Shortcuts")),
983 984 985
					$('th', null, localize('menuContexts', "Menu Contexts"))
				),
				...commands.map(c => $('tr', null,
986
					$('td', null, $('code', null, c.id)),
987
					$('td', null, c.title),
S
Sandeep Somavarapu 已提交
988
					$('td', null, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))),
989 990 991
					$('td', null, ...c.menus.map(context => $('code', null, context)))
				))
			)
J
Joao Moreno 已提交
992 993 994
		);

		append(container, details);
995
		return true;
996 997
	}

B
Benjamin Pasero 已提交
998
	private renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
999 1000
		const contributes = manifest.contributes;
		const rawLanguages = contributes && contributes.languages || [];
J
Joao Moreno 已提交
1001 1002
		const languages = rawLanguages.map(l => ({
			id: l.id,
J
Joao Moreno 已提交
1003
			name: (l.aliases || [])[0] || l.id,
J
Joao Moreno 已提交
1004 1005 1006
			extensions: l.extensions || [],
			hasGrammar: false,
			hasSnippets: false
J
Joao Moreno 已提交
1007 1008
		}));

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

J
Joao Moreno 已提交
1011
		const grammars = contributes && contributes.grammars || [];
J
Joao Moreno 已提交
1012 1013 1014 1015 1016

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

			if (!language) {
J
Joao Moreno 已提交
1017
				language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false };
J
Joao Moreno 已提交
1018 1019 1020 1021 1022 1023 1024
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasGrammar = true;
			}
		});

J
Joao Moreno 已提交
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
		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 已提交
1039
		if (!languages.length) {
1040
			return false;
J
Joao Moreno 已提交
1041 1042
		}

J
Joao Moreno 已提交
1043
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
1044 1045 1046
			$('summary', null, localize('languages', "Languages ({0})", languages.length)),
			$('table', null,
				$('tr', null,
J
Joao Moreno 已提交
1047 1048
					$('th', null, localize('language id', "ID")),
					$('th', null, localize('language name', "Name")),
J
Joao Moreno 已提交
1049
					$('th', null, localize('file extensions', "File Extensions")),
J
Joao Moreno 已提交
1050 1051
					$('th', null, localize('grammar', "Grammar")),
					$('th', null, localize('snippets', "Snippets"))
J
Joao Moreno 已提交
1052 1053
				),
				...languages.map(l => $('tr', null,
J
Joao Moreno 已提交
1054
					$('td', null, l.id),
J
Joao Moreno 已提交
1055
					$('td', null, l.name),
J
Joao Moreno 已提交
1056
					$('td', null, ...join(l.extensions.map(ext => $('code', null, ext)), ' ')),
J
Joao Moreno 已提交
1057 1058
					$('td', null, document.createTextNode(l.hasGrammar ? '✔︎' : '')),
					$('td', null, document.createTextNode(l.hasSnippets ? '✔︎' : ''))
J
Joao Moreno 已提交
1059 1060
				))
			)
J
Joao Moreno 已提交
1061 1062 1063
		);

		append(container, details);
1064
		return true;
J
Joao Moreno 已提交
1065 1066
	}

S
Sandeep Somavarapu 已提交
1067
	private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding {
1068 1069
		let key: string;

J
Johannes Rieken 已提交
1070
		switch (process.platform) {
1071 1072 1073 1074 1075
			case 'win32': key = rawKeyBinding.win; break;
			case 'linux': key = rawKeyBinding.linux; break;
			case 'darwin': key = rawKeyBinding.mac; break;
		}

J
Joao Moreno 已提交
1076
		const keyBinding = KeybindingParser.parseKeybinding(key || rawKeyBinding.key, OS);
A
Alex Dima 已提交
1077 1078 1079 1080
		if (!keyBinding) {
			return null;
		}

S
Sandeep Somavarapu 已提交
1081
		return this.keybindingService.resolveKeybinding(keyBinding)[0];
1082 1083
	}

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

1087 1088 1089 1090 1091
		const result = loadingTask();
		const onDone = () => removeClass(this.content, 'loading');
		result.promise.then(onDone, onDone);

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

1093
		return result.promise;
J
Joao Moreno 已提交
1094 1095 1096
	}

	layout(): void {
J
Joao Moreno 已提交
1097
		this.layoutParticipants.forEach(p => p.layout());
J
Joao Moreno 已提交
1098 1099
	}

1100 1101 1102 1103 1104
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

1105
		this.notificationService.error(err);
1106 1107
	}

J
Joao Moreno 已提交
1108
	dispose(): void {
J
Joao Moreno 已提交
1109 1110
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
1111 1112 1113
		super.dispose();
	}
}
1114

1115 1116 1117 1118 1119 1120 1121 1122 1123
class ShowExtensionEditorFindCommand extends Command {
	public runCommand(accessor: ServicesAccessor, args: any): void {
		const extensionEditor = this.getExtensionEditor(accessor);
		if (extensionEditor) {
			extensionEditor.showFind();
		}
	}

	private getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor {
1124
		const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor;
B
Benjamin Pasero 已提交
1125 1126
		if (activeControl instanceof ExtensionEditor) {
			return activeControl;
1127 1128 1129 1130 1131 1132
		}
		return null;
	}
}
const showCommand = new ShowExtensionEditorFindCommand({
	id: 'editor.action.extensioneditor.showfind',
1133
	precondition: ContextKeyExpr.equals('activeEditor', ExtensionEditor.ID),
1134
	kbOpts: {
A
Alex Dima 已提交
1135
		primary: KeyMod.CtrlCmd | KeyCode.KEY_F,
1136
		weight: KeybindingWeight.EditorContrib
1137 1138
	}
});
A
Alex Dima 已提交
1139
showCommand.register();