extensionEditor.ts 43.1 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

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

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

70 71 72 73 74 75 76 77 78
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 已提交
79
	return newDocument.documentElement.outerHTML;
80 81
}

J
Joao Moreno 已提交
82 83
class NavBar {

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

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

100
		action.tooltip = tooltip;
I
InspectorDeno 已提交
101

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
147 148
export class ExtensionEditor extends BaseEditor {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
236
		const extensionActions = append(details, $('.actions'));
237 238 239 240 241 242 243 244 245 246 247 248
		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 已提交
249

250
		this.recommendation = append(details, $('.recommendation'));
251 252 253 254 255
		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);
256

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

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

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

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

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

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

280 281 282 283
		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 已提交
284

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

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

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

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

300
		const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
301
		let recommendationsData = {};
302 303
		if (extRecommendations[extension.id.toLowerCase()]) {
			addClass(this.header, 'recommended');
304
			this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText;
305
			recommendationsData = { recommendationReason: extRecommendations[extension.id.toLowerCase()].reasonId };
306 307 308 309 310
		} 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 {
311
			this.recommendationText.textContent = '';
312 313
		}

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
382
		this.extensionActionBar.clear();
S
Sandeep Somavarapu 已提交
383 384
		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 已提交
385

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

		this.extensionTipsService.onRecommendationChange(change => {
392 393
			if (change.extensionId.toLowerCase() === extension.id.toLowerCase()) {
				if (change.isRecommended) {
394
					removeClass(this.header, 'recommendation-ignored');
395 396 397 398 399 400 401 402 403 404
					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.");
				}
405 406 407 408
			}
		});

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

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
466 467
		this.contentDisposables = dispose(this.contentDisposables);
		this.content.innerHTML = '';
S
Sandeep Somavarapu 已提交
468 469 470 471 472 473 474 475 476 477 478
		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 已提交
479
		switch (id) {
480 481 482
			case NavbarSection.Readme: return this.openReadme();
			case NavbarSection.Contributions: return this.openContributions();
			case NavbarSection.Changelog: return this.openChangelog();
483
			case NavbarSection.Dependencies: return this.openDependencies(extension);
484
			case NavbarSection.ExtensionPack: return this.openExtensionPack(extension);
J
Joao Moreno 已提交
485
		}
S
Sandeep Somavarapu 已提交
486
		return Promise.wrap(null);
J
Joao Moreno 已提交
487
	}
J
Joao Moreno 已提交
488

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

621 622 623 624 625 626 627 628 629 630 631 632
			get hasChildren(): boolean {
				return this.extensionDependencies.hasDependencies;
			}

			getChildren(): Promise<IExtensionData[]> {
				return this.extensionDependencies.dependencies ? TPromise.as(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : null;
			}
		}

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

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

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

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

	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)));
				}
				return TPromise.as(null);
			}
		}

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

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

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

J
Joao Moreno 已提交
699
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
700
			$('summary', null, localize('settings', "Settings ({0})", contrib.length)),
J
Joao Moreno 已提交
701
			$('table', null,
J
Joao Moreno 已提交
702 703 704
				$('tr', null,
					$('th', null, localize('setting name', "Name")),
					$('th', null, localize('description', "Description")),
J
nls  
Joao Moreno 已提交
705
					$('th', null, localize('default', "Default"))
J
Joao Moreno 已提交
706 707 708 709 710 711
				),
				...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 已提交
712
			)
J
Joao Moreno 已提交
713 714 715
		);

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

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

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

J
Joao Moreno 已提交
727
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
728
			$('summary', null, localize('debuggers', "Debuggers ({0})", contrib.length)),
J
Joao Moreno 已提交
729
			$('table', null,
730 731 732 733 734 735 736
				$('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 已提交
737
			)
J
Joao Moreno 已提交
738 739 740
		);

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

S
Sandeep Somavarapu 已提交
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 769
	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;
	}

770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
	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;
	}

796 797 798 799 800 801 802 803 804 805 806
	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 已提交
807
				$('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)"))),
808
				...localizations.map(localization => $('tr', null, $('td', null, localization.languageId), $('td', null, localization.languageName), $('td', null, localization.localizedLanguageName)))
809 810 811 812 813 814 815
			)
		);

		append(container, details);
		return true;
	}

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

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

J
Joao Moreno 已提交
824
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
			$('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 已提交
843 844 845 846
			$('ul', null, ...contrib.map(theme => $('li', null, theme.label)))
		);

		append(container, details);
847
		return true;
J
Joao Moreno 已提交
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 894
	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 已提交
895
	private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
896 897
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
898 899

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
977
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
978 979 980 981 982
			$('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 已提交
983
					$('th', null, localize('keyboard shortcuts', "Keyboard Shortcuts")),
984 985 986
					$('th', null, localize('menuContexts', "Menu Contexts"))
				),
				...commands.map(c => $('tr', null,
987
					$('td', null, $('code', null, c.id)),
988
					$('td', null, c.title),
S
Sandeep Somavarapu 已提交
989
					$('td', null, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))),
990 991 992
					$('td', null, ...c.menus.map(context => $('code', null, context)))
				))
			)
J
Joao Moreno 已提交
993 994 995
		);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1116 1117 1118 1119 1120 1121 1122 1123 1124
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 {
1125
		const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor;
B
Benjamin Pasero 已提交
1126 1127
		if (activeControl instanceof ExtensionEditor) {
			return activeControl;
1128 1129 1130 1131 1132 1133
		}
		return null;
	}
}
const showCommand = new ShowExtensionEditorFindCommand({
	id: 'editor.action.extensioneditor.showfind',
1134
	precondition: ContextKeyExpr.equals('activeEditor', ExtensionEditor.ID),
1135
	kbOpts: {
A
Alex Dima 已提交
1136
		primary: KeyMod.CtrlCmd | KeyCode.KEY_F,
1137
		weight: KeybindingWeight.EditorContrib
1138 1139
	}
});
A
Alex Dima 已提交
1140
showCommand.register();