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

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

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

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

J
Joao Moreno 已提交
83 84
class NavBar {

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

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

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

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

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
144 145 146 147
interface IActiveElement {
	focus(): 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

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

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

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

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

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

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

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

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

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

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

229 230 231 232
		this.repository = append(subtitle, $('span.repository.clickable'));
		this.repository.textContent = localize('repository', 'Repository');
		this.repository.style.display = 'none';

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

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

J
Joao Moreno 已提交
239
		const extensionActions = append(details, $('.actions'));
240 241 242
		this.extensionActionBar = new ActionBar(extensionActions, {
			animated: false,
			actionItemProvider: (action: Action) => {
S
Sandeep Somavarapu 已提交
243 244
				if (action instanceof ExtensionEditorDropDownAction) {
					return action.createActionItem();
245 246 247 248
				}
				return null;
			}
		});
J
Joao Moreno 已提交
249

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

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

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

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

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

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

273
	setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
S
Sandeep Somavarapu 已提交
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
		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)));

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

				this.name.textContent = extension.displayName;
				this.identifier.textContent = extension.id;
				this.preview.style.display = extension.preview ? 'inherit' : 'none';
				this.builtin.style.display = extension.type === LocalExtensionType.System ? 'inherit' : 'none';

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

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

				const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
				let recommendationsData = {};
				if (extRecommendations[extension.id.toLowerCase()]) {
					addClass(this.header, 'recommended');
					this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText;
					recommendationsData = { recommendationReason: extRecommendations[extension.id.toLowerCase()].reasonId };
				} 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 {
					this.recommendationText.textContent = '';
				}
315

S
Sandeep Somavarapu 已提交
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 342 343
				/* __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';
344 345
					}
				} else {
S
Sandeep Somavarapu 已提交
346 347 348 349 350
					this.name.onclick = null;
					this.rating.onclick = null;
					this.publisher.onclick = null;
					this.license.onclick = null;
					this.license.style.display = 'none';
351
				}
352

S
Sandeep Somavarapu 已提交
353 354 355 356 357 358 359 360
				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';
				}
361

S
Sandeep Somavarapu 已提交
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
				const install = this.instantiationService.createInstance(InstallCountWidget, this.installCount, { extension });
				this.transientDisposables.push(install);

				const ratings = this.instantiationService.createInstance(RatingsWidget, this.rating, { extension });
				this.transientDisposables.push(ratings);

				const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, true);
				const disabledStatusAction = this.instantiationService.createInstance(DisabledStatusLabelAction);
				const installAction = this.instantiationService.createInstance(CombinedInstallAction);
				const updateAction = this.instantiationService.createInstance(UpdateAction);
				const enableAction = this.instantiationService.createInstance(EnableDropDownAction, extension, runningExtensions);
				const disableAction = this.instantiationService.createInstance(DisableDropDownAction, extension, runningExtensions);
				const reloadAction = this.instantiationService.createInstance(ReloadAction, true);

				installAction.extension = extension;
				maliciousStatusAction.extension = extension;
				disabledStatusAction.extension = extension;
				updateAction.extension = extension;
				reloadAction.extension = extension;

				this.extensionActionBar.clear();
				this.extensionActionBar.push([reloadAction, updateAction, enableAction, disableAction, installAction, maliciousStatusAction, disabledStatusAction], { icon: true, label: true });
				this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, maliciousStatusAction, disabledStatusAction);

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

				this.extensionTipsService.onRecommendationChange(change => {
					if (change.extensionId.toLowerCase() === extension.id.toLowerCase()) {
						if (change.isRecommended) {
							removeClass(this.header, 'recommendation-ignored');
							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.");
						}
					}
				});
S
Sandeep Somavarapu 已提交
407

S
Sandeep Somavarapu 已提交
408 409 410
				this.ignoreActionbar.clear();
				this.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true });
				this.transientDisposables.push(ignoreAction, undoIgnoreAction);
S
Sandeep Somavarapu 已提交
411

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

				this.navbar.clear();
				this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);

				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 已提交
419
				}
S
Sandeep Somavarapu 已提交
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
				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 已提交
439
			});
J
Joao Moreno 已提交
440
	}
441

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			readonly extension: IExtension;
			readonly parent: IExtensionData;

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

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

			getChildren(): Promise<IExtensionData[]> {
				if (this.hasChildren) {
					const names = arrays.distinct(this.extension.extensionPack, e => e.toLowerCase());
					return extensionsWorkbenchService.queryGallery({ names, pageSize: names.length })
						.then(result => result.firstPage.map(extension => new ExtensionData(extension, this)));
				}
S
Sandeep Somavarapu 已提交
676
				return Promise.resolve(null);
677 678 679 680
			}
		}

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

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

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

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

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

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

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

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

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

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

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

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

		append(container, details);
		return true;
	}

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

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

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

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

851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
	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 已提交
896
	private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
897 898
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
899 900

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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