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

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

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

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

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

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

J
Joao Moreno 已提交
89 90
class NavBar {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
M
Matt Bierner 已提交
192 193 194
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IViewletService private readonly viewletService: IViewletService,
		@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
195
		@IThemeService protected themeService: IThemeService,
M
Matt Bierner 已提交
196 197 198 199 200
		@IKeybindingService private readonly keybindingService: IKeybindingService,
		@INotificationService private readonly notificationService: INotificationService,
		@IOpenerService private readonly openerService: IOpenerService,
		@IPartService private readonly partService: IPartService,
		@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
S
Sandeep Somavarapu 已提交
201
		@IStorageService storageService: IStorageService,
202
		@IExtensionService private readonly extensionService: IExtensionService
J
Joao Moreno 已提交
203
	) {
204
		super(ExtensionEditor.ID, telemetryService, themeService, storageService);
J
Joao Moreno 已提交
205
		this.disposables = [];
J
Joao Moreno 已提交
206
		this.extensionReadme = null;
X
XVincentX 已提交
207
		this.extensionChangelog = null;
J
Joao Moreno 已提交
208
		this.extensionManifest = null;
S
Sandeep Somavarapu 已提交
209
		this.extensionDependencies = null;
J
Joao Moreno 已提交
210 211
	}

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

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

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

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

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

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

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

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

237 238 239 240
		this.repository = append(subtitle, $('span.repository.clickable'));
		this.repository.textContent = localize('repository', 'Repository');
		this.repository.style.display = 'none';

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

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

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

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

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

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

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

J
Joao Moreno 已提交
275 276
		const body = append(root, $('.body'));
		this.navbar = new NavBar(body);
J
Joao Moreno 已提交
277

J
Joao Moreno 已提交
278
		this.content = append(body, $('.content'));
J
Joao Moreno 已提交
279 280
	}

J
Johannes Rieken 已提交
281
	setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Promise<void> {
S
Sandeep Somavarapu 已提交
282 283 284 285 286 287 288 289 290 291 292 293 294
		return this.extensionService.getExtensions()
			.then(runningExtensions => {
				this.activeElement = null;
				this.editorLoadComplete = false;
				const extension = input.extension;

				this.transientDisposables = dispose(this.transientDisposables);

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

295
				const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer);
J
Joao Moreno 已提交
296
				const onError = Event.once(domEvent(this.icon, 'error'));
S
Sandeep Somavarapu 已提交
297 298 299 300
				onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables);
				this.icon.src = extension.iconUrl;

				this.name.textContent = extension.displayName;
S
Sandeep Somavarapu 已提交
301
				this.identifier.textContent = extension.identifier.id;
S
Sandeep Somavarapu 已提交
302
				this.preview.style.display = extension.preview ? 'inherit' : 'none';
303
				this.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none';
S
Sandeep Somavarapu 已提交
304 305 306 307 308 309

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

				const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
				let recommendationsData = {};
S
Sandeep Somavarapu 已提交
310 311
				if (extRecommendations[extension.identifier.id.toLowerCase()]) {
					recommendationsData = { recommendationReason: extRecommendations[extension.identifier.id.toLowerCase()].reasonId };
S
Sandeep Somavarapu 已提交
312
				}
313

S
Sandeep Somavarapu 已提交
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
				/* __GDPR__
				"extensionGallery:openExtension" : {
					"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
					"${include}": [
						"${GalleryExtensionTelemetryData}"
					]
				}
				*/
				this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData));

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

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

S
Sandeep Somavarapu 已提交
351 352 353 354 355 356 357 358
				if (extension.repository) {
					this.repository.onclick = finalHandler(() => window.open(extension.repository));
					this.repository.style.display = 'initial';
				}
				else {
					this.repository.onclick = null;
					this.repository.style.display = 'none';
				}
359

S
Sandeep Somavarapu 已提交
360
				const widgets = [
361
					remoteBadge,
S
Sandeep Somavarapu 已提交
362 363 364
					this.instantiationService.createInstance(InstallCountWidget, this.installCount, false),
					this.instantiationService.createInstance(RatingsWidget, this.rating, false)
				];
365
				const reloadAction = this.instantiationService.createInstance(ReloadAction);
S
Sandeep Somavarapu 已提交
366 367
				const actions = [
					reloadAction,
S
#66931  
Sandeep Somavarapu 已提交
368
					this.instantiationService.createInstance(StatusLabelAction),
S
Sandeep Somavarapu 已提交
369
					this.instantiationService.createInstance(UpdateAction),
370
					this.instantiationService.createInstance(EnableDropDownAction),
S
Sandeep Somavarapu 已提交
371 372 373 374 375 376
					this.instantiationService.createInstance(DisableDropDownAction, runningExtensions),
					this.instantiationService.createInstance(CombinedInstallAction),
					this.instantiationService.createInstance(MaliciousStatusLabelAction, true),
				];
				const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]);
				extensionContainers.extension = extension;
J
Joao Moreno 已提交
377

S
Sandeep Somavarapu 已提交
378
				this.extensionActionBar.clear();
S
Sandeep Somavarapu 已提交
379
				this.extensionActionBar.push(actions, { icon: true, label: true });
380
				this.transientDisposables.push(...[...actions, ...widgets, extensionContainers]);
J
Joao Moreno 已提交
381

382
				this.setSubText(extension, reloadAction);
S
Sandeep Somavarapu 已提交
383
				this.content.innerHTML = ''; // Clear content before setting navbar actions.
S
Sandeep Somavarapu 已提交
384

S
Sandeep Somavarapu 已提交
385 386
				this.navbar.clear();
				this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
S
Sandeep Somavarapu 已提交
387

S
Sandeep Somavarapu 已提交
388 389
				if (extension.hasReadme()) {
					this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file"));
S
Sandeep Somavarapu 已提交
390
				}
S
Sandeep Somavarapu 已提交
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
				this.extensionManifest.get()
					.promise
					.then(manifest => {
						if (extension.extensionPack.length) {
							this.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together"));
						}
						if (manifest && manifest.contributes) {
							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;
					});

				return super.setInput(input, options, token);
S
Sandeep Somavarapu 已提交
410
			});
J
Joao Moreno 已提交
411
	}
412

413
	private setSubText(extension: IExtension, reloadAction: ReloadAction): void {
414 415 416 417 418 419 420 421 422 423 424 425 426 427
		hide(this.subtextContainer);

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

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

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

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

458
		this.transientDisposables.push(reloadAction.onDidChange(e => {
459
			if (e.tooltip) {
460 461 462 463 464
				this.subtext.textContent = reloadAction.tooltip;
				show(this.subtextContainer);
				ignoreAction.enabled = false;
				undoIgnoreAction.enabled = false;
			}
465 466 467 468 469 470 471
			if (e.enabled === true) {
				show(this.subtextContainer);
			}
			if (e.enabled === false) {
				hide(this.subtextContainer);
			}
		}));
472 473
	}

S
Sandeep Somavarapu 已提交
474 475 476 477 478 479
	focus(): void {
		if (this.activeElement) {
			this.activeElement.focus();
		}
	}

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

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

S
Sandeep Somavarapu 已提交
499 500
		this.contentDisposables = dispose(this.contentDisposables);
		this.content.innerHTML = '';
S
Sandeep Somavarapu 已提交
501 502 503 504 505 506 507 508 509 510
		this.activeElement = null;
		this.open(id, extension)
			.then(activeElement => {
				this.activeElement = activeElement;
				if (focus) {
					this.focus();
				}
			});
	}

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

S
Sandeep Somavarapu 已提交
522 523
	private openMarkdown(cacheResult: CacheResult<string>, noContentCopy: string): Promise<IActiveElement> {
		return this.loadContents(() => cacheResult)
J
Joao Moreno 已提交
524
			.then(marked.parse)
525
			.then(renderBody)
526
			.then(removeEmbeddedSVGs)
S
Sandeep Somavarapu 已提交
527
			.then(body => {
528
				const allowedBadgeProviders = this.extensionsWorkbenchService.allowedBadgeProviders;
529
				const webViewOptions = allowedBadgeProviders.length > 0 ? { allowScripts: false, allowSvgs: false, svgWhiteList: allowedBadgeProviders } : {};
S
Sandeep Somavarapu 已提交
530 531 532
				const wbeviewElement = this.instantiationService.createInstance(WebviewElement, this.partService.getContainer(Parts.EDITOR_PART), webViewOptions);
				wbeviewElement.mountTo(this.content);
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, wbeviewElement);
533
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));
S
Sandeep Somavarapu 已提交
534
				wbeviewElement.contents = body;
J
Joao Moreno 已提交
535

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

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

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

S
Sandeep Somavarapu 已提交
563
	private openContributions(): Promise<IActiveElement> {
S
Sandeep Somavarapu 已提交
564
		const content = $('div', { class: 'subcontent', tabindex: '0' });
S
Sandeep Somavarapu 已提交
565
		return this.loadContents(() => this.extensionManifest.get())
566
			.then(manifest => {
567
				const scrollableContent = new DomScrollableElement(content, {});
J
Joao Moreno 已提交
568 569 570 571 572

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

J
Joao Moreno 已提交
573
				const renders = [
B
Benjamin Pasero 已提交
574
					this.renderSettings(content, manifest, layout),
J
Joao Moreno 已提交
575
					this.renderCommands(content, manifest, layout),
B
Benjamin Pasero 已提交
576
					this.renderLanguages(content, manifest, layout),
577 578
					this.renderColorThemes(content, manifest, layout),
					this.renderIconThemes(content, manifest, layout),
579
					this.renderColors(content, manifest, layout),
B
Benjamin Pasero 已提交
580
					this.renderJSONValidation(content, manifest, layout),
581
					this.renderDebuggers(content, manifest, layout),
S
Sandeep Somavarapu 已提交
582
					this.renderViewContainers(content, manifest, layout),
583 584
					this.renderViews(content, manifest, layout),
					this.renderLocalizations(content, manifest, layout)
J
Joao Moreno 已提交
585 586 587
				];

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

				if (isEmpty) {
S
Sandeep Somavarapu 已提交
591 592
					append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
					append(this.content, content);
593 594 595 596
				} else {
					append(this.content, scrollableContent.getDomNode());
					this.contentDisposables.push(scrollableContent);
				}
S
Sandeep Somavarapu 已提交
597
				return content;
S
Sandeep Somavarapu 已提交
598
			}, () => {
S
Sandeep Somavarapu 已提交
599 600
				append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
				append(this.content, content);
S
Sandeep Somavarapu 已提交
601
				return content;
602
			});
J
Joao Moreno 已提交
603 604
	}

S
Sandeep Somavarapu 已提交
605
	private openDependencies(extension: IExtension): Promise<IActiveElement> {
606 607
		if (extension.dependencies.length === 0) {
			append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
S
Sandeep Somavarapu 已提交
608
			return Promise.resolve(this.content);
609
		}
S
Sandeep Somavarapu 已提交
610

S
Sandeep Somavarapu 已提交
611
		return this.loadContents(() => this.extensionDependencies.get())
J
Johannes Rieken 已提交
612
			.then<IActiveElement, IActiveElement>(extensionDependencies => {
613 614 615 616 617
				if (extensionDependencies) {
					const content = $('div', { class: 'subcontent' });
					const scrollableContent = new DomScrollableElement(content, {});
					append(this.content, scrollableContent.getDomNode());
					this.contentDisposables.push(scrollableContent);
618

619 620 621 622 623 624 625 626
					const dependenciesTree = this.renderDependencies(content, extensionDependencies);
					const layout = () => {
						scrollableContent.scanDomNode();
						const scrollDimensions = scrollableContent.getScrollDimensions();
						dependenciesTree.layout(scrollDimensions.height);
					};
					const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
					this.contentDisposables.push(toDisposable(removeLayoutParticipant));
S
Sandeep Somavarapu 已提交
627

628 629 630 631 632 633 634
					this.contentDisposables.push(dependenciesTree);
					scrollableContent.scanDomNode();
					return { focus() { dependenciesTree.domFocus(); } };
				} else {
					append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
					return Promise.resolve(this.content);
				}
635 636
			}, error => {
				append(this.content, $('p.nocontent')).textContent = error;
637
				this.notificationService.error(error);
S
Sandeep Somavarapu 已提交
638
				return this.content;
639
			});
S
Sandeep Somavarapu 已提交
640 641
	}

642
	private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): Tree {
643 644 645
		class ExtensionData implements IExtensionData {

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

647 648 649 650 651 652 653
			constructor(extensionDependencies: IExtensionDependencies) {
				this.extensionDependencies = extensionDependencies;
			}

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

655
			get parent(): IExtensionData | null {
656
				return this.extensionDependencies.dependent ? new ExtensionData(this.extensionDependencies.dependent) : null;
657 658
			}

659 660 661 662
			get hasChildren(): boolean {
				return this.extensionDependencies.hasDependencies;
			}

663 664
			getChildren(): Promise<IExtensionData[] | null> {
				return this.extensionDependencies.dependencies ? Promise.resolve(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : Promise.resolve(null);
665 666 667 668 669 670
			}
		}

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

S
Sandeep Somavarapu 已提交
671
	private openExtensionPack(extension: IExtension): Promise<IActiveElement> {
672 673 674 675
		const content = $('div', { class: 'subcontent' });
		const scrollableContent = new DomScrollableElement(content, {});
		append(this.content, scrollableContent.getDomNode());
		this.contentDisposables.push(scrollableContent);
676

S
Sandeep Somavarapu 已提交
677
		const extensionsPackTree = this.renderExtensionPack(content, extension);
678
		const layout = () => {
679
			scrollableContent.scanDomNode();
680
			const scrollDimensions = scrollableContent.getScrollDimensions();
S
Sandeep Somavarapu 已提交
681
			extensionsPackTree.layout(scrollDimensions.height);
682 683 684 685
		};
		const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
		this.contentDisposables.push(toDisposable(removeLayoutParticipant));

S
Sandeep Somavarapu 已提交
686
		this.contentDisposables.push(extensionsPackTree);
687
		scrollableContent.scanDomNode();
S
Sandeep Somavarapu 已提交
688
		return Promise.resolve({ focus() { extensionsPackTree.domFocus(); } });
689 690 691 692 693 694 695
	}

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

			readonly extension: IExtension;
696
			readonly parent: IExtensionData | null;
697 698 699

			constructor(extension: IExtension, parent?: IExtensionData) {
				this.extension = extension;
700
				this.parent = parent || null;
701 702 703 704 705 706
			}

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

707
			getChildren(): Promise<IExtensionData[] | null> {
708 709 710 711 712
				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 已提交
713
				return Promise.resolve(null);
714 715 716 717
			}
		}

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

B
Benjamin Pasero 已提交
720
	private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
721 722
		const contributes = manifest.contributes;
		const configuration = contributes && contributes.configuration;
723 724 725 726 727 728 729 730
		let properties = {};
		if (Array.isArray(configuration)) {
			configuration.forEach(config => {
				properties = { ...properties, ...config.properties };
			});
		} else if (configuration) {
			properties = configuration.properties;
		}
J
Joao Moreno 已提交
731
		const contrib = properties ? Object.keys(properties) : [];
J
Joao Moreno 已提交
732

J
Joao Moreno 已提交
733
		if (!contrib.length) {
734
			return false;
J
Joao Moreno 已提交
735 736
		}

J
Joao Moreno 已提交
737
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
738 739 740 741 742 743
			$('summary', undefined, localize('settings', "Settings ({0})", contrib.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('setting name', "Name")),
					$('th', undefined, localize('description', "Description")),
					$('th', undefined, localize('default', "Default"))
J
Joao Moreno 已提交
744
				),
745 746 747 748
				...contrib.map(key => $('tr', undefined,
					$('td', undefined, $('code', undefined, key)),
					$('td', undefined, properties[key].description),
					$('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`))
J
Joao Moreno 已提交
749
				))
J
Joao Moreno 已提交
750
			)
J
Joao Moreno 已提交
751 752 753
		);

		append(container, details);
754
		return true;
J
Joao Moreno 已提交
755 756
	}

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

J
Joao Moreno 已提交
761
		if (!contrib.length) {
762
			return false;
J
Joao Moreno 已提交
763 764
		}

J
Joao Moreno 已提交
765
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
766 767 768 769 770
			$('summary', undefined, localize('debuggers', "Debuggers ({0})", contrib.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('debugger name', "Name")),
					$('th', undefined, localize('debugger type', "Type")),
771
				),
772 773 774
				...contrib.map(d => $('tr', undefined,
					$('td', undefined, d.label!),
					$('td', undefined, d.type)))
J
Joao Moreno 已提交
775
			)
J
Joao Moreno 已提交
776 777 778
		);

		append(container, details);
779
		return true;
J
Joao Moreno 已提交
780 781
	}

S
Sandeep Somavarapu 已提交
782 783 784 785
	private renderViewContainers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.viewsContainers || {};

786
		let viewContainers = Object.keys(contrib).reduce((result, location) => {
S
Sandeep Somavarapu 已提交
787 788 789
			let viewContainersForLocation: IViewContainer[] = contrib[location];
			result.push(...viewContainersForLocation.map(viewContainer => ({ ...viewContainer, location })));
			return result;
790
		}, [] as Array<{ id: string, title: string, location: string }>);
S
Sandeep Somavarapu 已提交
791 792 793 794 795 796

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

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
797 798 799 800
			$('summary', undefined, localize('viewContainers', "View Containers ({0})", viewContainers.length)),
			$('table', undefined,
				$('tr', undefined, $('th', undefined, localize('view container id', "ID")), $('th', undefined, localize('view container title', "Title")), $('th', undefined, localize('view container location', "Where"))),
				...viewContainers.map(viewContainer => $('tr', undefined, $('td', undefined, viewContainer.id), $('td', undefined, viewContainer.title), $('td', undefined, viewContainer.location)))
S
Sandeep Somavarapu 已提交
801 802 803 804 805 806 807
			)
		);

		append(container, details);
		return true;
	}

808 809 810 811
	private renderViews(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.views || {};

812
		let views = Object.keys(contrib).reduce((result, location) => {
813 814 815
			let viewsForLocation: IView[] = contrib[location];
			result.push(...viewsForLocation.map(view => ({ ...view, location })));
			return result;
816
		}, [] as Array<{ id: string, name: string, location: string }>);
817 818 819 820 821 822

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

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
823 824 825 826
			$('summary', undefined, localize('views', "Views ({0})", views.length)),
			$('table', undefined,
				$('tr', undefined, $('th', undefined, localize('view id', "ID")), $('th', undefined, localize('view name', "Name")), $('th', undefined, localize('view location', "Where"))),
				...views.map(view => $('tr', undefined, $('td', undefined, view.id), $('td', undefined, view.name), $('td', undefined, view.location)))
827 828 829 830 831 832 833
			)
		);

		append(container, details);
		return true;
	}

834 835 836 837 838 839 840 841 842
	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 },
843 844 845 846
			$('summary', undefined, localize('localizations', "Localizations ({0})", localizations.length)),
			$('table', undefined,
				$('tr', undefined, $('th', undefined, localize('localizations language id', "Language Id")), $('th', undefined, localize('localizations language name', "Language Name")), $('th', undefined, localize('localizations localized language name', "Language Name (Localized)"))),
				...localizations.map(localization => $('tr', undefined, $('td', undefined, localization.languageId), $('td', undefined, localization.languageName || ''), $('td', undefined, localization.localizedLanguageName || '')))
847 848 849 850 851 852 853
			)
		);

		append(container, details);
		return true;
	}

854
	private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
855 856
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.themes || [];
J
Joao Moreno 已提交
857

J
Joao Moreno 已提交
858
		if (!contrib.length) {
859
			return false;
J
Joao Moreno 已提交
860 861
		}

J
Joao Moreno 已提交
862
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
863 864
			$('summary', undefined, localize('colorThemes', "Color Themes ({0})", contrib.length)),
			$('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label)))
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
		);

		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 },
880 881
			$('summary', undefined, localize('iconThemes', "Icon Themes ({0})", contrib.length)),
			$('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label)))
J
Joao Moreno 已提交
882 883 884
		);

		append(container, details);
885
		return true;
J
Joao Moreno 已提交
886 887
	}

888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903
	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) }, ''));
				}
			}
904
			result.push($('code', undefined, colorReference));
905 906 907 908
			return result;
		}

		const details = $('details', { open: true, ontoggle: onDetailsToggle },
909 910 911 912 913 914 915 916
			$('summary', undefined, localize('colors', "Colors ({0})", colors.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('colorId', "Id")),
					$('th', undefined, localize('description', "Description")),
					$('th', undefined, localize('defaultDark', "Dark Default")),
					$('th', undefined, localize('defaultLight', "Light Default")),
					$('th', undefined, localize('defaultHC', "High Contrast Default"))
917
				),
918 919 920 921 922 923
				...colors.map(color => $('tr', undefined,
					$('td', undefined, $('code', undefined, color.id)),
					$('td', undefined, color.description),
					$('td', undefined, ...colorPreview(color.defaults.dark)),
					$('td', undefined, ...colorPreview(color.defaults.light)),
					$('td', undefined, ...colorPreview(color.defaults.highContrast))
924 925 926 927 928 929 930 931 932
				))
			)
		);

		append(container, details);
		return true;
	}


B
Benjamin Pasero 已提交
933
	private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
934 935
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
936 937

		if (!contrib.length) {
938
			return false;
J
Joao Moreno 已提交
939 940
		}

J
Joao Moreno 已提交
941
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
942 943 944 945 946
			$('summary', undefined, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('fileMatch', "File Match")),
					$('th', undefined, localize('schema', "Schema"))
S
Sandeep Somavarapu 已提交
947
				),
948 949 950
				...contrib.map(v => $('tr', undefined,
					$('td', undefined, $('code', undefined, v.fileMatch)),
					$('td', undefined, v.url)
S
Sandeep Somavarapu 已提交
951
				))));
J
Joao Moreno 已提交
952 953

		append(container, details);
954
		return true;
J
Joao Moreno 已提交
955 956
	}

957
	private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
958 959
		const contributes = manifest.contributes;
		const rawCommands = contributes && contributes.commands || [];
J
Joao Moreno 已提交
960
		const commands = rawCommands.map(c => ({
961 962
			id: c.command,
			title: c.title,
963 964
			keybindings: [] as ResolvedKeybinding[],
			menus: [] as string[]
965 966
		}));

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

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

971 972
		Object.keys(menus).forEach(context => {
			menus[context].forEach(menu => {
J
Joao Moreno 已提交
973
				let command = byId[menu.command];
974 975

				if (!command) {
J
Joao Moreno 已提交
976
					command = { id: menu.command, title: '', keybindings: [], menus: [context] };
J
Joao Moreno 已提交
977
					byId[command.id] = command;
978 979 980 981 982 983 984
					commands.push(command);
				} else {
					command.menus.push(context);
				}
			});
		});

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

987
		rawKeybindings.forEach(rawKeybinding => {
S
Sandeep Somavarapu 已提交
988
			const keybinding = this.resolveKeybinding(rawKeybinding);
J
Joao Moreno 已提交
989

S
Sandeep Somavarapu 已提交
990
			if (!keybinding) {
J
Joao Moreno 已提交
991 992 993
				return;
			}

J
Joao Moreno 已提交
994
			let command = byId[rawKeybinding.command];
J
Joao Moreno 已提交
995 996

			if (!command) {
S
Sandeep Somavarapu 已提交
997
				command = { id: rawKeybinding.command, title: '', keybindings: [keybinding], menus: [] };
J
Joao Moreno 已提交
998
				byId[command.id] = command;
J
Joao Moreno 已提交
999 1000
				commands.push(command);
			} else {
S
Sandeep Somavarapu 已提交
1001
				command.keybindings.push(keybinding);
J
Joao Moreno 已提交
1002 1003 1004
			}
		});

1005
		if (!commands.length) {
1006
			return false;
1007 1008
		}

S
Sandeep Somavarapu 已提交
1009 1010
		const renderKeybinding = (keybinding: ResolvedKeybinding): HTMLElement => {
			const element = $('');
1011
			new KeybindingLabel(element, OS).set(keybinding);
S
Sandeep Somavarapu 已提交
1012 1013 1014
			return element;
		};

J
Joao Moreno 已提交
1015
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
1016 1017 1018 1019 1020 1021 1022
			$('summary', undefined, localize('commands', "Commands ({0})", commands.length)),
			$('table', undefined,
				$('tr', undefined,
					$('th', undefined, localize('command name', "Name")),
					$('th', undefined, localize('description', "Description")),
					$('th', undefined, localize('keyboard shortcuts', "Keyboard Shortcuts")),
					$('th', undefined, localize('menuContexts', "Menu Contexts"))
1023
				),
1024 1025 1026 1027 1028
				...commands.map(c => $('tr', undefined,
					$('td', undefined, $('code', undefined, c.id)),
					$('td', undefined, c.title),
					$('td', undefined, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))),
					$('td', undefined, ...c.menus.map(context => $('code', undefined, context)))
1029 1030
				))
			)
J
Joao Moreno 已提交
1031 1032 1033
		);

		append(container, details);
1034
		return true;
1035 1036
	}

B
Benjamin Pasero 已提交
1037
	private renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
1038 1039
		const contributes = manifest.contributes;
		const rawLanguages = contributes && contributes.languages || [];
J
Joao Moreno 已提交
1040 1041
		const languages = rawLanguages.map(l => ({
			id: l.id,
J
Joao Moreno 已提交
1042
			name: (l.aliases || [])[0] || l.id,
J
Joao Moreno 已提交
1043 1044 1045
			extensions: l.extensions || [],
			hasGrammar: false,
			hasSnippets: false
J
Joao Moreno 已提交
1046 1047
		}));

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

J
Joao Moreno 已提交
1050
		const grammars = contributes && contributes.grammars || [];
J
Joao Moreno 已提交
1051 1052 1053 1054 1055

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

			if (!language) {
J
Joao Moreno 已提交
1056
				language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false };
J
Joao Moreno 已提交
1057 1058 1059 1060 1061 1062 1063
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasGrammar = true;
			}
		});

J
Joao Moreno 已提交
1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
		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 已提交
1078
		if (!languages.length) {
1079
			return false;
J
Joao Moreno 已提交
1080 1081
		}

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

		append(container, details);
1103
		return true;
J
Joao Moreno 已提交
1104 1105
	}

1106 1107
	private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | null {
		let key: string | undefined;
1108

J
Johannes Rieken 已提交
1109
		switch (process.platform) {
1110 1111 1112 1113 1114
			case 'win32': key = rawKeyBinding.win; break;
			case 'linux': key = rawKeyBinding.linux; break;
			case 'darwin': key = rawKeyBinding.mac; break;
		}

J
Joao Moreno 已提交
1115
		const keyBinding = KeybindingParser.parseKeybinding(key || rawKeyBinding.key, OS);
A
Alex Dima 已提交
1116 1117 1118 1119
		if (!keyBinding) {
			return null;
		}

S
Sandeep Somavarapu 已提交
1120
		return this.keybindingService.resolveKeybinding(keyBinding)[0];
1121 1122
	}

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

1126 1127 1128 1129 1130
		const result = loadingTask();
		const onDone = () => removeClass(this.content, 'loading');
		result.promise.then(onDone, onDone);

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

1132
		return result.promise;
J
Joao Moreno 已提交
1133 1134 1135
	}

	layout(): void {
J
Joao Moreno 已提交
1136
		this.layoutParticipants.forEach(p => p.layout());
J
Joao Moreno 已提交
1137 1138
	}

1139 1140 1141 1142 1143
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

1144
		this.notificationService.error(err);
1145 1146
	}

J
Joao Moreno 已提交
1147
	dispose(): void {
J
Joao Moreno 已提交
1148 1149
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
1150 1151 1152
		super.dispose();
	}
}
1153

1154 1155 1156 1157 1158 1159 1160 1161
class ShowExtensionEditorFindCommand extends Command {
	public runCommand(accessor: ServicesAccessor, args: any): void {
		const extensionEditor = this.getExtensionEditor(accessor);
		if (extensionEditor) {
			extensionEditor.showFind();
		}
	}

1162
	private getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor | null {
1163
		const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor;
B
Benjamin Pasero 已提交
1164 1165
		if (activeControl instanceof ExtensionEditor) {
			return activeControl;
1166 1167 1168 1169 1170 1171
		}
		return null;
	}
}
const showCommand = new ShowExtensionEditorFindCommand({
	id: 'editor.action.extensioneditor.showfind',
1172
	precondition: ContextKeyExpr.equals('activeEditor', ExtensionEditor.ID),
1173
	kbOpts: {
A
Alex Dima 已提交
1174
		primary: KeyMod.CtrlCmd | KeyCode.KEY_F,
1175
		weight: KeybindingWeight.EditorContrib
1176 1177
	}
});
A
Alex Dima 已提交
1178
showCommand.register();