extensionEditor.ts 44.4 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';
J
Joao Moreno 已提交
12
import { always } 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';
J
Joao Moreno 已提交
16
import Cache 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, MultiServerInstallAction, MultiServerUpdateAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
M
Matt Bierner 已提交
34
import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement';
A
Alex Dima 已提交
35
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
J
Johannes Rieken 已提交
36
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
J
Joao Moreno 已提交
37
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
38
import { IOpenerService } from 'vs/platform/opener/common/opener';
S
Sandeep Somavarapu 已提交
39
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
40
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
41
import { IThemeService } from 'vs/platform/theme/common/themeService';
S
Sandeep Somavarapu 已提交
42
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
M
Matt Bierner 已提交
43
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
44
import { Command } from 'vs/editor/browser/editorExtensions';
45
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
46
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
47
import { Color } from 'vs/base/common/color';
48
import { assign } from 'vs/base/common/objects';
49
import { INotificationService } from 'vs/platform/notification/common/notification';
50
import { CancellationToken } from 'vs/base/common/cancellation';
51
import { ExtensionsTree, IExtensionData } from 'vs/workbench/parts/extensions/browser/extensionsViewer';
52 53 54

/**  A context key that is set when an extension editor webview has focus. */
export const KEYBINDING_CONTEXT_EXTENSIONEDITOR_WEBVIEW_FOCUS = new RawContextKey<boolean>('extensionEditorWebviewFocus', undefined);
55 56
/**  A context key that is set when the find widget find input in extension editor webview is focused. */
export const KEYBINDING_CONTEXT_EXTENSIONEDITOR_FIND_WIDGET_INPUT_FOCUSED = new RawContextKey<boolean>('extensionEditorFindWidgetInputFocused', false);
J
Joao Moreno 已提交
57

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

74 75 76 77 78 79 80 81 82
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 已提交
83
	return newDocument.documentElement.outerHTML;
84 85
}

J
Joao Moreno 已提交
86 87 88 89 90
class NavBar {

	private _onChange = new Emitter<string>();
	get onChange(): Event<string> { return this._onChange.event; }

J
Joao Moreno 已提交
91
	private currentId: string = null;
J
Joao Moreno 已提交
92 93 94 95 96 97 98 99 100
	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 已提交
101
	push(id: string, label: string, tooltip: string): void {
J
Joao Moreno 已提交
102
		const run = () => this._update(id);
J
Joao Moreno 已提交
103 104
		const action = new Action(id, label, null, true, run);

105
		action.tooltip = tooltip;
I
InspectorDeno 已提交
106

J
Joao Moreno 已提交
107 108 109 110 111 112 113 114 115 116 117 118 119
		this.actions.push(action);
		this.actionbar.push(action);

		if (this.actions.length === 1) {
			run();
		}
	}

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

J
Joao Moreno 已提交
120 121 122 123 124 125 126 127 128 129 130
	update(): void {
		this._update(this.currentId);
	}

	_update(id: string = this.currentId): TPromise<void> {
		this.currentId = id;
		this._onChange.fire(id);
		this.actions.forEach(a => a.enabled = a.id !== id);
		return TPromise.as(null);
	}

J
Joao Moreno 已提交
131 132 133 134 135 136 137
	dispose(): void {
		this.actionbar = dispose(this.actionbar);
	}
}

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

J
Joao Moreno 已提交
144 145 146 147
interface ILayoutParticipant {
	layout(): void;
}

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

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

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

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

176
	private contextKey: IContextKey<boolean>;
177
	private findInputFocusContextKey: IContextKey<boolean>;
J
Joao Moreno 已提交
178
	private layoutParticipants: ILayoutParticipant[] = [];
J
Joao Moreno 已提交
179 180
	private contentDisposables: IDisposable[] = [];
	private transientDisposables: IDisposable[] = [];
J
Joao Moreno 已提交
181
	private disposables: IDisposable[];
M
Matt Bierner 已提交
182
	private activeWebview: WebviewElement;
183
	private editorLoadComplete: boolean = false;
J
Joao Moreno 已提交
184 185 186

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
M
Matt Bierner 已提交
187 188 189
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IViewletService private readonly viewletService: IViewletService,
		@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
190
		@IThemeService protected themeService: IThemeService,
M
Matt Bierner 已提交
191 192 193 194 195 196
		@IKeybindingService private readonly keybindingService: IKeybindingService,
		@INotificationService private readonly notificationService: INotificationService,
		@IOpenerService private readonly openerService: IOpenerService,
		@IPartService private readonly partService: IPartService,
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
		@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
J
Joao Moreno 已提交
197
	) {
B
Benjamin Pasero 已提交
198
		super(ExtensionEditor.ID, telemetryService, themeService);
J
Joao Moreno 已提交
199
		this.disposables = [];
J
Joao Moreno 已提交
200
		this.extensionReadme = null;
X
XVincentX 已提交
201
		this.extensionChangelog = null;
J
Joao Moreno 已提交
202
		this.extensionManifest = null;
S
Sandeep Somavarapu 已提交
203
		this.extensionDependencies = null;
204 205
		this.contextKey = KEYBINDING_CONTEXT_EXTENSIONEDITOR_WEBVIEW_FOCUS.bindTo(this.contextKeyService);
		this.findInputFocusContextKey = KEYBINDING_CONTEXT_EXTENSIONEDITOR_FIND_WIDGET_INPUT_FOCUSED.bindTo(this.contextKeyService);
J
Joao Moreno 已提交
206 207
	}

208 209
	createEditor(parent: HTMLElement): void {
		const root = append(parent, $('.extension-editor'));
210
		this.header = append(root, $('.header'));
J
Joao Moreno 已提交
211

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

214
		const details = append(this.header, $('.details'));
J
Joao Moreno 已提交
215
		const title = append(details, $('.title'));
J
Joao Moreno 已提交
216
		this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") }));
J
Joao Moreno 已提交
217
		this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") }));
S
Sandeep Somavarapu 已提交
218

219
		this.preview = append(title, $('span.preview', { title: localize('preview', "Preview") }));
S
Sandeep Somavarapu 已提交
220 221 222 223
		this.preview.textContent = localize('preview', "Preview");

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

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

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

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

232 233 234 235
		this.repository = append(subtitle, $('span.repository.clickable'));
		this.repository.textContent = localize('repository', 'Repository');
		this.repository.style.display = 'none';

J
Joao Moreno 已提交
236
		this.license = append(subtitle, $('span.license.clickable'));
J
Joao Moreno 已提交
237
		this.license.textContent = localize('license', 'License');
238
		this.license.style.display = 'none';
J
Joao Moreno 已提交
239

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

J
Joao Moreno 已提交
242
		const extensionActions = append(details, $('.actions'));
243 244 245 246 247 248 249 250 251
		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;
				}
252 253 254 255 256 257
				if (action.id === MultiServerInstallAction.ID) {
					return (<MultiServerInstallAction>action).actionItem;
				}
				if (action.id === MultiServerUpdateAction.ID) {
					return (<MultiServerUpdateAction>action).actionItem;
				}
258 259 260
				return null;
			}
		});
J
Joao Moreno 已提交
261

262
		this.recommendation = append(details, $('.recommendation'));
263 264 265 266 267
		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);
268

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

274 275 276 277 278
		chain(this.ignoreActionbar.onDidRun)
			.map(({ error }) => error)
			.filter(error => !!error)
			.on(this.onError, this, this.disposables);

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

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

285
	setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
286
		this.editorLoadComplete = false;
J
Joao Moreno 已提交
287
		const extension = input.extension;
288
		const servers = input.servers;
J
Joao Moreno 已提交
289

J
Joao Moreno 已提交
290 291
		this.transientDisposables = dispose(this.transientDisposables);

J
Joao Moreno 已提交
292
		this.extensionReadme = new Cache(() => extension.getReadme());
X
XVincentX 已提交
293
		this.extensionChangelog = new Cache(() => extension.getChangelog());
J
Joao Moreno 已提交
294
		this.extensionManifest = new Cache(() => extension.getManifest());
S
Sandeep Somavarapu 已提交
295
		this.extensionDependencies = new Cache(() => this.extensionsWorkbenchService.loadDependencies(extension));
J
Joao Moreno 已提交
296

297 298 299 300
		const onError = once(domEvent(this.icon, 'error'));
		onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables);
		this.icon.src = extension.iconUrl;

J
Joao Moreno 已提交
301
		this.name.textContent = extension.displayName;
S
Sandeep Somavarapu 已提交
302
		this.identifier.textContent = extension.id;
S
Sandeep Somavarapu 已提交
303 304
		this.preview.style.display = extension.preview ? 'inherit' : 'none';
		this.builtin.style.display = extension.type === LocalExtensionType.System ? 'inherit' : 'none';
305

J
Joao Moreno 已提交
306 307
		this.publisher.textContent = extension.publisherDisplayName;
		this.description.textContent = extension.description;
J
Joao Moreno 已提交
308

309
		removeClass(this.header, 'recommendation-ignored');
310 311
		removeClass(this.header, 'recommended');

312
		const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
313
		let recommendationsData = {};
314 315
		if (extRecommendations[extension.id.toLowerCase()]) {
			addClass(this.header, 'recommended');
316
			this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText;
317
			recommendationsData = { recommendationReason: extRecommendations[extension.id.toLowerCase()].reasonId };
318 319 320 321 322
		} 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 {
323
			this.recommendationText.textContent = '';
324 325
		}

326
		/* __GDPR__
327 328 329 330 331 332
		"extensionGallery:openExtension" : {
			"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
			"${include}": [
				"${GalleryExtensionTelemetryData}"
			]
		}
333 334 335
		*/
		this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData));

S
Sandeep Somavarapu 已提交
336 337 338
		toggleClass(this.name, 'clickable', !!extension.url);
		toggleClass(this.publisher, 'clickable', !!extension.url);
		toggleClass(this.rating, 'clickable', !!extension.url);
339 340 341
		if (extension.url) {
			this.name.onclick = finalHandler(() => window.open(extension.url));
			this.rating.onclick = finalHandler(() => window.open(`${extension.url}#review-details`));
J
Joao Moreno 已提交
342
			this.publisher.onclick = finalHandler(() => {
343
				this.viewletService.openViewlet(VIEWLET_ID, true)
J
Joao Moreno 已提交
344
					.then(viewlet => viewlet as IExtensionsViewlet)
J
Johannes Rieken 已提交
345
					.done(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`));
J
Joao Moreno 已提交
346
			});
347 348

			if (extension.licenseUrl) {
B
Benjamin Pasero 已提交
349
				this.license.onclick = finalHandler(() => window.open(extension.licenseUrl));
350 351 352 353 354
				this.license.style.display = 'initial';
			} else {
				this.license.onclick = null;
				this.license.style.display = 'none';
			}
S
Sandeep Somavarapu 已提交
355 356 357 358
		} else {
			this.name.onclick = null;
			this.rating.onclick = null;
			this.publisher.onclick = null;
J
Joao Moreno 已提交
359 360
		}

361 362 363 364 365 366 367 368 369
		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';
		}

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

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

J
Joao Moreno 已提交
376
		const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, true);
S
Sandeep Somavarapu 已提交
377
		const disabledStatusAction = this.instantiationService.createInstance(DisabledStatusLabelAction);
378
		const installAction = this.instantiationService.createInstance(CombinedInstallAction);
379
		const updateAction = servers.length === 1 ? this.instantiationService.createInstance(UpdateAction) : this.instantiationService.createInstance(MultiServerUpdateAction);
J
Joao Moreno 已提交
380
		const enableAction = this.instantiationService.createInstance(EnableAction);
381
		const disableAction = this.instantiationService.createInstance(DisableAction);
382
		const reloadAction = this.instantiationService.createInstance(ReloadAction);
J
Joao Moreno 已提交
383 384

		installAction.extension = extension;
J
Joao Moreno 已提交
385
		maliciousStatusAction.extension = extension;
S
Sandeep Somavarapu 已提交
386
		disabledStatusAction.extension = extension;
J
Joao Moreno 已提交
387 388
		updateAction.extension = extension;
		enableAction.extension = extension;
389
		disableAction.extension = extension;
390
		reloadAction.extension = extension;
J
Joao Moreno 已提交
391

J
Joao Moreno 已提交
392
		this.extensionActionBar.clear();
S
Sandeep Somavarapu 已提交
393 394
		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 已提交
395

396
		const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction);
397
		const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction);
398
		ignoreAction.extension = extension;
399
		undoIgnoreAction.extension = extension;
400 401

		this.extensionTipsService.onRecommendationChange(change => {
402 403
			if (change.extensionId.toLowerCase() === extension.id.toLowerCase()) {
				if (change.isRecommended) {
404
					removeClass(this.header, 'recommendation-ignored');
405 406 407 408 409 410 411 412 413 414
					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.");
				}
415 416 417 418
			}
		});

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

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

J
Joao Moreno 已提交
424 425
		this.navbar.clear();
		this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
I
InspectorDeno 已提交
426
		this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file"));
427 428 429
		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 已提交
430
		this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension"));
I
InspectorDeno 已提交
431
		this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file"));
432 433 434
		if (extension.dependencies.length) {
			this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on"));
		}
J
Joao Moreno 已提交
435

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

440 441 442 443 444 445
	showFind(): void {
		if (this.activeWebview) {
			this.activeWebview.showFind();
		}
	}

J
Joao Moreno 已提交
446
	private onNavbarChange(extension: IExtension, id: string): void {
447 448 449
		if (this.editorLoadComplete) {
			/* __GDPR__
				"extensionEditor:navbarChange" : {
450
					"navItem": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
451 452 453 454 455 456 457 458
					"${include}": [
						"${GalleryExtensionTelemetryData}"
					]
				}
			*/
			this.telemetryService.publicLog('extensionEditor:navbarChange', assign(extension.telemetryData, { navItem: id }));
		}

S
Sandeep Somavarapu 已提交
459 460
		this.contentDisposables = dispose(this.contentDisposables);
		this.content.innerHTML = '';
461
		this.activeWebview = null;
J
Joao Moreno 已提交
462
		switch (id) {
463 464 465
			case NavbarSection.Readme: return this.openReadme();
			case NavbarSection.Contributions: return this.openContributions();
			case NavbarSection.Changelog: return this.openChangelog();
466
			case NavbarSection.Dependencies: return this.openDependencies(extension);
467
			case NavbarSection.ExtensionPack: return this.openExtensionPack(extension);
J
Joao Moreno 已提交
468
		}
J
Joao Moreno 已提交
469
	}
J
Joao Moreno 已提交
470

471
	private openMarkdown(content: TPromise<string>, noContentCopy: string) {
472
		return this.loadContents(() => content
J
Joao Moreno 已提交
473
			.then(marked.parse)
474
			.then(renderBody)
475
			.then(removeEmbeddedSVGs)
J
Joao Moreno 已提交
476
			.then<void>(body => {
477
				const allowedBadgeProviders = this.extensionsWorkbenchService.allowedBadgeProviders;
478
				const webViewOptions = allowedBadgeProviders.length > 0 ? { allowScripts: false, allowSvgs: false, svgWhiteList: allowedBadgeProviders } : {};
479
				this.activeWebview = this.instantiationService.createInstance(WebviewElement, this.partService.getContainer(Parts.EDITOR_PART), this.contextKey, this.findInputFocusContextKey, webViewOptions);
M
Matt Bierner 已提交
480
				this.activeWebview.mountTo(this.content);
481
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, this.activeWebview);
482
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));
483
				this.activeWebview.contents = body;
J
Joao Moreno 已提交
484

485
				this.activeWebview.onDidClickLink(link => {
M
Matt Bierner 已提交
486 487 488 489 490
					// Whitelist supported schemes for links
					if (link && ['http', 'https', 'mailto'].indexOf(link.scheme) >= 0) {
						this.openerService.open(link);
					}
				}, null, this.contentDisposables);
491
				this.contentDisposables.push(this.activeWebview);
J
Joao Moreno 已提交
492
			})
J
Joao Moreno 已提交
493
			.then(null, () => {
494
				const p = append(this.content, $('p.nocontent'));
X
XVincentX 已提交
495
				p.textContent = noContentCopy;
J
Joao Moreno 已提交
496
			}));
J
Joao Moreno 已提交
497
	}
J
Joao Moreno 已提交
498

499 500
	private openReadme() {
		return this.openMarkdown(this.extensionReadme.get(), localize('noReadme', "No README available."));
501 502
	}

503
	private openChangelog() {
504
		return this.openMarkdown(this.extensionChangelog.get(), localize('noChangelog', "No Changelog available."));
505 506
	}

507
	private openContributions() {
J
Joao Moreno 已提交
508
		return this.loadContents(() => this.extensionManifest.get()
509
			.then(manifest => {
J
Joao Moreno 已提交
510
				const content = $('div', { class: 'subcontent' });
511
				const scrollableContent = new DomScrollableElement(content, {});
J
Joao Moreno 已提交
512 513 514 515 516

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

J
Joao Moreno 已提交
517
				const renders = [
B
Benjamin Pasero 已提交
518
					this.renderSettings(content, manifest, layout),
J
Joao Moreno 已提交
519
					this.renderCommands(content, manifest, layout),
B
Benjamin Pasero 已提交
520
					this.renderLanguages(content, manifest, layout),
521 522
					this.renderColorThemes(content, manifest, layout),
					this.renderIconThemes(content, manifest, layout),
523
					this.renderColors(content, manifest, layout),
B
Benjamin Pasero 已提交
524
					this.renderJSONValidation(content, manifest, layout),
525
					this.renderDebuggers(content, manifest, layout),
S
Sandeep Somavarapu 已提交
526
					this.renderViewContainers(content, manifest, layout),
527 528
					this.renderViews(content, manifest, layout),
					this.renderLocalizations(content, manifest, layout)
J
Joao Moreno 已提交
529 530 531
				];

				const isEmpty = !renders.reduce((v, r) => r || v, false);
J
Joao Moreno 已提交
532
				scrollableContent.scanDomNode();
533 534 535 536 537 538 539 540

				if (isEmpty) {
					append(this.content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
					return;
				} else {
					append(this.content, scrollableContent.getDomNode());
					this.contentDisposables.push(scrollableContent);
				}
S
Sandeep Somavarapu 已提交
541 542
			}, () => {
				append(this.content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
543
			}));
J
Joao Moreno 已提交
544 545
	}

546 547 548 549 550
	private openDependencies(extension: IExtension) {
		if (extension.dependencies.length === 0) {
			append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
			return;
		}
S
Sandeep Somavarapu 已提交
551

552 553 554
		return this.loadContents(() => {
			return this.extensionDependencies.get().then(extensionDependencies => {
				const content = $('div', { class: 'subcontent' });
555
				const scrollableContent = new DomScrollableElement(content, {});
556 557 558
				append(this.content, scrollableContent.getDomNode());
				this.contentDisposables.push(scrollableContent);

559
				const dependenciesTree = this.renderDependencies(content, extensionDependencies);
560 561
				const layout = () => {
					scrollableContent.scanDomNode();
562
					const scrollDimensions = scrollableContent.getScrollDimensions();
563
					dependenciesTree.layout(scrollDimensions.height);
564 565 566
				};
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));
S
Sandeep Somavarapu 已提交
567

568
				this.contentDisposables.push(dependenciesTree);
569
				scrollableContent.scanDomNode();
570 571
			}, error => {
				append(this.content, $('p.nocontent')).textContent = error;
572
				this.notificationService.error(error);
573
			});
S
Sandeep Somavarapu 已提交
574 575 576
		});
	}

577
	private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): Tree {
578 579 580
		class ExtensionData implements IExtensionData {

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

582 583 584 585 586 587 588
			constructor(extensionDependencies: IExtensionDependencies) {
				this.extensionDependencies = extensionDependencies;
			}

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

590 591
			get parent(): IExtensionData {
				return this.extensionDependencies.dependent ? new ExtensionData(this.extensionDependencies.dependent) : null;
592 593
			}

594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
			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);
	}

	private openExtensionPack(extension: IExtension) {
		return this.loadContents(() => {
			const content = $('div', { class: 'subcontent' });
			const scrollableContent = new DomScrollableElement(content, {});
			append(this.content, scrollableContent.getDomNode());
			this.contentDisposables.push(scrollableContent);

			const dependenciesTree = this.renderExtensionPack(content, extension);
			const layout = () => {
				scrollableContent.scanDomNode();
				const scrollDimensions = scrollableContent.getScrollDimensions();
				dependenciesTree.layout(scrollDimensions.height);
			};
			const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
			this.contentDisposables.push(toDisposable(removeLayoutParticipant));

			this.contentDisposables.push(dependenciesTree);
			scrollableContent.scanDomNode();
			return TPromise.as(null);
		});
	}

	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 已提交
655 656
	}

B
Benjamin Pasero 已提交
657
	private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
658 659
		const contributes = manifest.contributes;
		const configuration = contributes && contributes.configuration;
J
Joao Moreno 已提交
660
		const properties = configuration && configuration.properties;
J
Joao Moreno 已提交
661
		const contrib = properties ? Object.keys(properties) : [];
J
Joao Moreno 已提交
662

J
Joao Moreno 已提交
663
		if (!contrib.length) {
664
			return false;
J
Joao Moreno 已提交
665 666
		}

J
Joao Moreno 已提交
667
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
668
			$('summary', null, localize('settings', "Settings ({0})", contrib.length)),
J
Joao Moreno 已提交
669
			$('table', null,
J
Joao Moreno 已提交
670 671 672
				$('tr', null,
					$('th', null, localize('setting name', "Name")),
					$('th', null, localize('description', "Description")),
J
nls  
Joao Moreno 已提交
673
					$('th', null, localize('default', "Default"))
J
Joao Moreno 已提交
674 675 676 677 678 679
				),
				...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 已提交
680
			)
J
Joao Moreno 已提交
681 682 683
		);

		append(container, details);
684
		return true;
J
Joao Moreno 已提交
685 686
	}

687 688


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

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

J
Joao Moreno 已提交
697
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
698
			$('summary', null, localize('debuggers', "Debuggers ({0})", contrib.length)),
J
Joao Moreno 已提交
699
			$('table', null,
700 701 702 703 704 705 706
				$('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 已提交
707
			)
J
Joao Moreno 已提交
708 709 710
		);

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

S
Sandeep Somavarapu 已提交
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739
	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;
	}

740 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
	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;
	}

766 767 768 769 770 771 772 773 774 775 776
	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 已提交
777
				$('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)"))),
778
				...localizations.map(localization => $('tr', null, $('td', null, localization.languageId), $('td', null, localization.languageName), $('td', null, localization.localizedLanguageName)))
779 780 781 782 783 784 785
			)
		);

		append(container, details);
		return true;
	}

786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
	/* private renderExtensionPack(container: HTMLElement, extension: IExtension, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		if (!manifest.contributes || !manifest.contributes.extensionPack) {
			return false;
		}

		const extensionsWorkbenchService = this.extensionsWorkbenchService;
		class ExtensionData implements IExtensionData {

			readonly extension: IExtension;
			readonly parent: IExtensionData;
			private readonly manifest: IExtensionManifest;

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

			get hasChildren(): boolean {
				return !!(this.manifest.contributes && this.manifest.contributes.extensionPack);
			}

			getChildren(): Promise<IExtensionData[]> {
				if (this.manifest.contributes && this.manifest.contributes.extensionPack) {
					const names = arrays.distinct(this.manifest.contributes.extensionPack, e => e.id.toLowerCase()).map(({ id }) => id);
					return extensionsWorkbenchService.queryGallery({ names, pageSize: names.length })
						.then(result => TPromise.join(result.firstPage.map(e => e.getManifest()))
							.then(manifests => result.firstPage.map((extension, index) => new ExtensionData(extension, manifests[index], this))));
				}
				return TPromise.as(null);
			}
		}

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
			$('summary', null, localize('extension pack', "Extension Pack")));

		const tree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, manifest), details);
		append(container, details);

		details.style.height = '1000px';
		tree.layout(1000);
		return true;
	} */

830
	private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
831 832
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.themes || [];
J
Joao Moreno 已提交
833

J
Joao Moreno 已提交
834
		if (!contrib.length) {
835
			return false;
J
Joao Moreno 已提交
836 837
		}

J
Joao Moreno 已提交
838
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
			$('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 已提交
857 858 859 860
			$('ul', null, ...contrib.map(theme => $('li', null, theme.label)))
		);

		append(container, details);
861
		return true;
J
Joao Moreno 已提交
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 895 896 897 898 899 900 901 902 903 904 905 906 907 908
	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 已提交
909
	private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
910 911
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
912 913

		if (!contrib.length) {
914
			return false;
J
Joao Moreno 已提交
915 916
		}

J
Joao Moreno 已提交
917
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
918
			$('summary', null, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
S
Sandeep Somavarapu 已提交
919 920 921 922 923 924
			$('table', null,
				$('tr', null,
					$('th', null, localize('fileMatch', "File Match")),
					$('th', null, localize('schema', "Schema"))
				),
				...contrib.map(v => $('tr', null,
S
Sandeep Somavarapu 已提交
925
					$('td', null, $('code', null, v.fileMatch)),
S
Sandeep Somavarapu 已提交
926 927
					$('td', null, v.url)
				))));
J
Joao Moreno 已提交
928 929

		append(container, details);
930
		return true;
J
Joao Moreno 已提交
931 932
	}

933
	private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
934 935
		const contributes = manifest.contributes;
		const rawCommands = contributes && contributes.commands || [];
J
Joao Moreno 已提交
936
		const commands = rawCommands.map(c => ({
937 938
			id: c.command,
			title: c.title,
J
Joao Moreno 已提交
939
			keybindings: [],
940 941 942
			menus: []
		}));

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

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

947 948
		Object.keys(menus).forEach(context => {
			menus[context].forEach(menu => {
J
Joao Moreno 已提交
949
				let command = byId[menu.command];
950 951

				if (!command) {
J
Joao Moreno 已提交
952
					command = { id: menu.command, title: '', keybindings: [], menus: [context] };
J
Joao Moreno 已提交
953
					byId[command.id] = command;
954 955 956 957 958 959 960
					commands.push(command);
				} else {
					command.menus.push(context);
				}
			});
		});

J
Joao Moreno 已提交
961
		const rawKeybindings = contributes && contributes.keybindings || [];
J
Joao Moreno 已提交
962

963
		rawKeybindings.forEach(rawKeybinding => {
S
Sandeep Somavarapu 已提交
964
			const keybinding = this.resolveKeybinding(rawKeybinding);
J
Joao Moreno 已提交
965

S
Sandeep Somavarapu 已提交
966
			if (!keybinding) {
J
Joao Moreno 已提交
967 968 969
				return;
			}

J
Joao Moreno 已提交
970
			let command = byId[rawKeybinding.command];
J
Joao Moreno 已提交
971 972

			if (!command) {
S
Sandeep Somavarapu 已提交
973
				command = { id: rawKeybinding.command, title: '', keybindings: [keybinding], menus: [] };
J
Joao Moreno 已提交
974
				byId[command.id] = command;
J
Joao Moreno 已提交
975 976
				commands.push(command);
			} else {
S
Sandeep Somavarapu 已提交
977
				command.keybindings.push(keybinding);
J
Joao Moreno 已提交
978 979 980
			}
		});

981
		if (!commands.length) {
982
			return false;
983 984
		}

S
Sandeep Somavarapu 已提交
985 986 987 988 989 990
		const renderKeybinding = (keybinding: ResolvedKeybinding): HTMLElement => {
			const element = $('');
			new KeybindingLabel(element, OS).set(keybinding, null);
			return element;
		};

J
Joao Moreno 已提交
991
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
992 993 994 995 996
			$('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 已提交
997
					$('th', null, localize('keyboard shortcuts', "Keyboard Shortcuts")),
998 999 1000
					$('th', null, localize('menuContexts', "Menu Contexts"))
				),
				...commands.map(c => $('tr', null,
1001
					$('td', null, $('code', null, c.id)),
1002
					$('td', null, c.title),
S
Sandeep Somavarapu 已提交
1003
					$('td', null, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))),
1004 1005 1006
					$('td', null, ...c.menus.map(context => $('code', null, context)))
				))
			)
J
Joao Moreno 已提交
1007 1008 1009
		);

		append(container, details);
1010
		return true;
1011 1012
	}

B
Benjamin Pasero 已提交
1013
	private renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
1014 1015
		const contributes = manifest.contributes;
		const rawLanguages = contributes && contributes.languages || [];
J
Joao Moreno 已提交
1016 1017
		const languages = rawLanguages.map(l => ({
			id: l.id,
J
Joao Moreno 已提交
1018
			name: (l.aliases || [])[0] || l.id,
J
Joao Moreno 已提交
1019 1020 1021
			extensions: l.extensions || [],
			hasGrammar: false,
			hasSnippets: false
J
Joao Moreno 已提交
1022 1023
		}));

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

J
Joao Moreno 已提交
1026
		const grammars = contributes && contributes.grammars || [];
J
Joao Moreno 已提交
1027 1028 1029 1030 1031

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

			if (!language) {
J
Joao Moreno 已提交
1032
				language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false };
J
Joao Moreno 已提交
1033 1034 1035 1036 1037 1038 1039
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasGrammar = true;
			}
		});

J
Joao Moreno 已提交
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
		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 已提交
1054
		if (!languages.length) {
1055
			return false;
J
Joao Moreno 已提交
1056 1057
		}

J
Joao Moreno 已提交
1058
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
1059 1060 1061
			$('summary', null, localize('languages', "Languages ({0})", languages.length)),
			$('table', null,
				$('tr', null,
J
Joao Moreno 已提交
1062 1063
					$('th', null, localize('language id', "ID")),
					$('th', null, localize('language name', "Name")),
J
Joao Moreno 已提交
1064
					$('th', null, localize('file extensions', "File Extensions")),
J
Joao Moreno 已提交
1065 1066
					$('th', null, localize('grammar', "Grammar")),
					$('th', null, localize('snippets', "Snippets"))
J
Joao Moreno 已提交
1067 1068
				),
				...languages.map(l => $('tr', null,
J
Joao Moreno 已提交
1069
					$('td', null, l.id),
J
Joao Moreno 已提交
1070
					$('td', null, l.name),
J
Joao Moreno 已提交
1071
					$('td', null, ...join(l.extensions.map(ext => $('code', null, ext)), ' ')),
J
Joao Moreno 已提交
1072 1073
					$('td', null, document.createTextNode(l.hasGrammar ? '✔︎' : '')),
					$('td', null, document.createTextNode(l.hasSnippets ? '✔︎' : ''))
J
Joao Moreno 已提交
1074 1075
				))
			)
J
Joao Moreno 已提交
1076 1077 1078
		);

		append(container, details);
1079
		return true;
J
Joao Moreno 已提交
1080 1081
	}

S
Sandeep Somavarapu 已提交
1082
	private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding {
1083 1084
		let key: string;

J
Johannes Rieken 已提交
1085
		switch (process.platform) {
1086 1087 1088 1089 1090
			case 'win32': key = rawKeyBinding.win; break;
			case 'linux': key = rawKeyBinding.linux; break;
			case 'darwin': key = rawKeyBinding.mac; break;
		}

1091
		const keyBinding = KeybindingIO.readKeybinding(key || rawKeyBinding.key, OS);
A
Alex Dima 已提交
1092 1093 1094 1095
		if (!keyBinding) {
			return null;
		}

S
Sandeep Somavarapu 已提交
1096
		return this.keybindingService.resolveKeybinding(keyBinding)[0];
1097 1098
	}

J
Johannes Rieken 已提交
1099
	private loadContents(loadingTask: () => TPromise<any>): void {
J
Joao Moreno 已提交
1100
		addClass(this.content, 'loading');
J
Joao Moreno 已提交
1101

J
Joao Moreno 已提交
1102 1103
		let promise = loadingTask();
		promise = always(promise, () => removeClass(this.content, 'loading'));
J
Joao Moreno 已提交
1104

J
Joao Moreno 已提交
1105
		this.contentDisposables.push(toDisposable(() => promise.cancel()));
J
Joao Moreno 已提交
1106 1107 1108
	}

	layout(): void {
J
Joao Moreno 已提交
1109
		this.layoutParticipants.forEach(p => p.layout());
J
Joao Moreno 已提交
1110 1111
	}

1112 1113 1114 1115 1116
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

1117
		this.notificationService.error(err);
1118 1119
	}

J
Joao Moreno 已提交
1120
	dispose(): void {
J
Joao Moreno 已提交
1121 1122
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
1123 1124 1125
		super.dispose();
	}
}
1126

1127 1128 1129 1130 1131 1132 1133 1134 1135
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 {
1136
		const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor;
B
Benjamin Pasero 已提交
1137 1138
		if (activeControl instanceof ExtensionEditor) {
			return activeControl;
1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150
		}
		return null;
	}
}
const showCommand = new ShowExtensionEditorFindCommand({
	id: 'editor.action.extensioneditor.showfind',
	precondition: KEYBINDING_CONTEXT_EXTENSIONEDITOR_WEBVIEW_FOCUS,
	kbOpts: {
		primary: KeyMod.CtrlCmd | KeyCode.KEY_F
	}
});
KeybindingsRegistry.registerCommandAndKeybindingRule(showCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));