extensionEditor.ts 43.1 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

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

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

J
Joao Moreno 已提交
79 80
class NavBar {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
275 276
		this.transientDisposables = dispose(this.transientDisposables);

277 278 279 280
		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 已提交
281

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

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

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

294
		removeClass(this.header, 'recommendation-ignored');
295 296
		removeClass(this.header, 'recommended');

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	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 已提交
672
				return Promise.resolve(null);
673 674 675 676
			}
		}

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

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

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
	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;
	}

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

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

		append(container, details);
		return true;
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

964
		if (!commands.length) {
965
			return false;
966 967
		}

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

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

		append(container, details);
993
		return true;
994 995
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1091
		return result.promise;
J
Joao Moreno 已提交
1092 1093 1094
	}

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

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

1103
		this.notificationService.error(err);
1104 1105
	}

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

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