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

'use strict';

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
242
		const extensionActions = append(details, $('.actions'));
243 244 245 246 247 248 249 250 251
		this.extensionActionBar = new ActionBar(extensionActions, {
			animated: false,
			actionItemProvider: (action: Action) => {
				if (action.id === EnableAction.ID) {
					return (<EnableAction>action).actionItem;
				}
				if (action.id === DisableAction.ID) {
					return (<DisableAction>action).actionItem;
				}
252 253 254 255 256 257
				if (action.id === MultiServerInstallAction.ID) {
					return (<MultiServerInstallAction>action).actionItem;
				}
				if (action.id === MultiServerUpdateAction.ID) {
					return (<MultiServerUpdateAction>action).actionItem;
				}
258 259 260
				return null;
			}
		});
J
Joao Moreno 已提交
261

262
		this.recommendation = append(details, $('.recommendation'));
263 264 265 266 267
		this.recommendationText = append(this.recommendation, $('.recommendation-text'));
		this.ignoreActionbar = new ActionBar(this.recommendation, { animated: false });

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

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

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

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

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

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

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

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

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

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

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

309
		removeClass(this.header, 'recommendation-ignored');
310
		const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
311
		let recommendationsData = {};
312 313
		if (extRecommendations[extension.id.toLowerCase()]) {
			addClass(this.header, 'recommended');
314
			this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText;
315
			recommendationsData = { recommendationReason: extRecommendations[extension.id.toLowerCase()].reasonId };
316 317
		} else {
			removeClass(this.header, 'recommended');
318
			this.recommendationText.textContent = '';
319 320
		}

321
		/* __GDPR__
322 323 324 325 326 327
		"extensionGallery:openExtension" : {
			"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
			"${include}": [
				"${GalleryExtensionTelemetryData}"
			]
		}
328 329 330
		*/
		this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData));

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

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

356 357 358 359 360 361 362 363 364
		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';
		}

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

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

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

		installAction.extension = extension;
J
Joao Moreno 已提交
380
		maliciousStatusAction.extension = extension;
S
Sandeep Somavarapu 已提交
381
		disabledStatusAction.extension = extension;
J
Joao Moreno 已提交
382 383
		updateAction.extension = extension;
		enableAction.extension = extension;
384
		disableAction.extension = extension;
385
		reloadAction.extension = extension;
J
Joao Moreno 已提交
386

J
Joao Moreno 已提交
387
		this.extensionActionBar.clear();
S
Sandeep Somavarapu 已提交
388 389
		this.extensionActionBar.push([disabledStatusAction, reloadAction, updateAction, enableAction, disableAction, installAction, maliciousStatusAction], { icon: true, label: true });
		this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, maliciousStatusAction, disabledStatusAction);
J
Joao Moreno 已提交
390

391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
		const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction);
		ignoreAction.extension = extension;

		this.extensionTipsService.onRecommendationChange(change => {
			if (change.extensionId.toLowerCase() === extension.id.toLowerCase() && change.isRecommended === false) {
				addClass(this.header, 'recommendation-ignored');
				removeClass(this.header, 'recommended');
				this.recommendationText.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
			}
		});

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

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

J
Joao Moreno 已提交
408 409
		this.navbar.clear();
		this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
I
InspectorDeno 已提交
410
		this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file"));
S
Sandeep Somavarapu 已提交
411
		this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension"));
I
InspectorDeno 已提交
412
		this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file"));
S
Sandeep Somavarapu 已提交
413
		this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on"));
J
Joao Moreno 已提交
414

415
		this.editorLoadComplete = true;
416
		return super.setInput(input, options, token);
J
Joao Moreno 已提交
417
	}
418

419 420 421 422 423 424
	showFind(): void {
		if (this.activeWebview) {
			this.activeWebview.showFind();
		}
	}

J
Joao Moreno 已提交
425
	private onNavbarChange(extension: IExtension, id: string): void {
426 427 428
		if (this.editorLoadComplete) {
			/* __GDPR__
				"extensionEditor:navbarChange" : {
429
					"navItem": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
430 431 432 433 434 435 436 437
					"${include}": [
						"${GalleryExtensionTelemetryData}"
					]
				}
			*/
			this.telemetryService.publicLog('extensionEditor:navbarChange', assign(extension.telemetryData, { navItem: id }));
		}

S
Sandeep Somavarapu 已提交
438 439
		this.contentDisposables = dispose(this.contentDisposables);
		this.content.innerHTML = '';
440
		this.activeWebview = null;
J
Joao Moreno 已提交
441
		switch (id) {
442 443 444
			case NavbarSection.Readme: return this.openReadme();
			case NavbarSection.Contributions: return this.openContributions();
			case NavbarSection.Changelog: return this.openChangelog();
445
			case NavbarSection.Dependencies: return this.openDependencies(extension);
J
Joao Moreno 已提交
446
		}
J
Joao Moreno 已提交
447
	}
J
Joao Moreno 已提交
448

449
	private openMarkdown(content: TPromise<string>, noContentCopy: string) {
450
		return this.loadContents(() => content
J
Joao Moreno 已提交
451
			.then(marked.parse)
452
			.then(renderBody)
453
			.then(removeEmbeddedSVGs)
J
Joao Moreno 已提交
454
			.then<void>(body => {
455
				const allowedBadgeProviders = this.extensionsWorkbenchService.allowedBadgeProviders;
456
				const webViewOptions = allowedBadgeProviders.length > 0 ? { allowScripts: false, allowSvgs: false, svgWhiteList: allowedBadgeProviders } : {};
457
				this.activeWebview = this.instantiationService.createInstance(WebviewElement, this.partService.getContainer(Parts.EDITOR_PART), this.contextKey, this.findInputFocusContextKey, webViewOptions);
M
Matt Bierner 已提交
458
				this.activeWebview.mountTo(this.content);
459
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, this.activeWebview);
460
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));
461
				this.activeWebview.contents = body;
J
Joao Moreno 已提交
462

463
				this.activeWebview.onDidClickLink(link => {
M
Matt Bierner 已提交
464 465 466 467 468
					// Whitelist supported schemes for links
					if (link && ['http', 'https', 'mailto'].indexOf(link.scheme) >= 0) {
						this.openerService.open(link);
					}
				}, null, this.contentDisposables);
469
				this.contentDisposables.push(this.activeWebview);
J
Joao Moreno 已提交
470
			})
J
Joao Moreno 已提交
471
			.then(null, () => {
472
				const p = append(this.content, $('p.nocontent'));
X
XVincentX 已提交
473
				p.textContent = noContentCopy;
J
Joao Moreno 已提交
474
			}));
J
Joao Moreno 已提交
475
	}
J
Joao Moreno 已提交
476

477 478
	private openReadme() {
		return this.openMarkdown(this.extensionReadme.get(), localize('noReadme', "No README available."));
479 480
	}

481
	private openChangelog() {
482
		return this.openMarkdown(this.extensionChangelog.get(), localize('noChangelog', "No Changelog available."));
483 484
	}

485
	private openContributions() {
J
Joao Moreno 已提交
486
		return this.loadContents(() => this.extensionManifest.get()
487
			.then(manifest => {
J
Joao Moreno 已提交
488
				const content = $('div', { class: 'subcontent' });
489
				const scrollableContent = new DomScrollableElement(content, {});
J
Joao Moreno 已提交
490 491 492 493 494

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

J
Joao Moreno 已提交
495
				const renders = [
B
Benjamin Pasero 已提交
496
					this.renderSettings(content, manifest, layout),
J
Joao Moreno 已提交
497
					this.renderCommands(content, manifest, layout),
B
Benjamin Pasero 已提交
498
					this.renderLanguages(content, manifest, layout),
499 500
					this.renderColorThemes(content, manifest, layout),
					this.renderIconThemes(content, manifest, layout),
501
					this.renderColors(content, manifest, layout),
B
Benjamin Pasero 已提交
502
					this.renderJSONValidation(content, manifest, layout),
503
					this.renderDebuggers(content, manifest, layout),
S
Sandeep Somavarapu 已提交
504
					this.renderViewContainers(content, manifest, layout),
505 506
					this.renderViews(content, manifest, layout),
					this.renderLocalizations(content, manifest, layout)
J
Joao Moreno 已提交
507 508 509
				];

				const isEmpty = !renders.reduce((v, r) => r || v, false);
J
Joao Moreno 已提交
510
				scrollableContent.scanDomNode();
511 512 513 514 515 516 517 518

				if (isEmpty) {
					append(this.content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
					return;
				} else {
					append(this.content, scrollableContent.getDomNode());
					this.contentDisposables.push(scrollableContent);
				}
S
Sandeep Somavarapu 已提交
519 520
			}, () => {
				append(this.content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
521
			}));
J
Joao Moreno 已提交
522 523
	}

524 525 526 527 528
	private openDependencies(extension: IExtension) {
		if (extension.dependencies.length === 0) {
			append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
			return;
		}
S
Sandeep Somavarapu 已提交
529

530 531 532
		return this.loadContents(() => {
			return this.extensionDependencies.get().then(extensionDependencies => {
				const content = $('div', { class: 'subcontent' });
533
				const scrollableContent = new DomScrollableElement(content, {});
534 535 536 537 538 539
				append(this.content, scrollableContent.getDomNode());
				this.contentDisposables.push(scrollableContent);

				const tree = this.renderDependencies(content, extensionDependencies);
				const layout = () => {
					scrollableContent.scanDomNode();
540 541
					const scrollDimensions = scrollableContent.getScrollDimensions();
					tree.layout(scrollDimensions.height);
542 543 544
				};
				const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
				this.contentDisposables.push(toDisposable(removeLayoutParticipant));
S
Sandeep Somavarapu 已提交
545

546
				this.contentDisposables.push(tree);
547
				scrollableContent.scanDomNode();
548 549
			}, error => {
				append(this.content, $('p.nocontent')).textContent = error;
550
				this.notificationService.error(error);
551
			});
S
Sandeep Somavarapu 已提交
552 553 554
		});
	}

555 556 557
	private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): Tree {
		const renderer = this.instantiationService.createInstance(Renderer);
		const controller = this.instantiationService.createInstance(Controller);
558
		const tree = this.instantiationService.createInstance(WorkbenchTree, container, {
S
Sandeep Somavarapu 已提交
559
			dataSource: new DataSource(),
560 561
			renderer,
			controller
S
Sandeep Somavarapu 已提交
562 563
		}, {
				indentPixels: 40,
564
				twistiePixels: 20
565
			});
B
Benjamin Pasero 已提交
566

S
Sandeep Somavarapu 已提交
567
		tree.setInput(extensionDependencies);
568

569
		this.contentDisposables.push(tree.onDidChangeSelection(event => {
570 571 572 573 574
			if (event && event.payload && event.payload.origin === 'keyboard') {
				controller.openExtension(tree, false);
			}
		}));

S
Sandeep Somavarapu 已提交
575 576 577
		return tree;
	}

B
Benjamin Pasero 已提交
578
	private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
579 580
		const contributes = manifest.contributes;
		const configuration = contributes && contributes.configuration;
J
Joao Moreno 已提交
581
		const properties = configuration && configuration.properties;
J
Joao Moreno 已提交
582
		const contrib = properties ? Object.keys(properties) : [];
J
Joao Moreno 已提交
583

J
Joao Moreno 已提交
584
		if (!contrib.length) {
585
			return false;
J
Joao Moreno 已提交
586 587
		}

J
Joao Moreno 已提交
588
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
589
			$('summary', null, localize('settings', "Settings ({0})", contrib.length)),
J
Joao Moreno 已提交
590
			$('table', null,
J
Joao Moreno 已提交
591 592 593
				$('tr', null,
					$('th', null, localize('setting name', "Name")),
					$('th', null, localize('description', "Description")),
J
nls  
Joao Moreno 已提交
594
					$('th', null, localize('default', "Default"))
J
Joao Moreno 已提交
595 596 597 598 599 600
				),
				...contrib.map(key => $('tr', null,
					$('td', null, $('code', null, key)),
					$('td', null, properties[key].description),
					$('td', null, $('code', null, properties[key].default))
				))
J
Joao Moreno 已提交
601
			)
J
Joao Moreno 已提交
602 603 604
		);

		append(container, details);
605
		return true;
J
Joao Moreno 已提交
606 607
	}

608 609


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

J
Joao Moreno 已提交
614
		if (!contrib.length) {
615
			return false;
J
Joao Moreno 已提交
616 617
		}

J
Joao Moreno 已提交
618
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
619
			$('summary', null, localize('debuggers', "Debuggers ({0})", contrib.length)),
J
Joao Moreno 已提交
620
			$('table', null,
621 622 623 624 625 626 627
				$('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 已提交
628
			)
J
Joao Moreno 已提交
629 630 631
		);

		append(container, details);
632
		return true;
J
Joao Moreno 已提交
633 634
	}

S
Sandeep Somavarapu 已提交
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
	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;
	}

661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
	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;
	}

687 688 689 690 691 692 693 694 695 696 697
	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 已提交
698
				$('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)"))),
699
				...localizations.map(localization => $('tr', null, $('td', null, localization.languageId), $('td', null, localization.languageName), $('td', null, localization.localizedLanguageName)))
700 701 702 703 704 705 706
			)
		);

		append(container, details);
		return true;
	}

707
	private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
708 709
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.themes || [];
J
Joao Moreno 已提交
710

J
Joao Moreno 已提交
711
		if (!contrib.length) {
712
			return false;
J
Joao Moreno 已提交
713 714
		}

J
Joao Moreno 已提交
715
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
			$('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 已提交
734 735 736 737
			$('ul', null, ...contrib.map(theme => $('li', null, theme.label)))
		);

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

741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
	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 已提交
786
	private renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
787 788
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
789 790

		if (!contrib.length) {
791
			return false;
J
Joao Moreno 已提交
792 793
		}

J
Joao Moreno 已提交
794
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
795
			$('summary', null, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
S
Sandeep Somavarapu 已提交
796 797 798 799 800 801
			$('table', null,
				$('tr', null,
					$('th', null, localize('fileMatch', "File Match")),
					$('th', null, localize('schema', "Schema"))
				),
				...contrib.map(v => $('tr', null,
S
Sandeep Somavarapu 已提交
802
					$('td', null, $('code', null, v.fileMatch)),
S
Sandeep Somavarapu 已提交
803 804
					$('td', null, v.url)
				))));
J
Joao Moreno 已提交
805 806

		append(container, details);
807
		return true;
J
Joao Moreno 已提交
808 809
	}

810
	private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
811 812
		const contributes = manifest.contributes;
		const rawCommands = contributes && contributes.commands || [];
J
Joao Moreno 已提交
813
		const commands = rawCommands.map(c => ({
814 815
			id: c.command,
			title: c.title,
J
Joao Moreno 已提交
816
			keybindings: [],
817 818 819
			menus: []
		}));

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

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

824 825
		Object.keys(menus).forEach(context => {
			menus[context].forEach(menu => {
J
Joao Moreno 已提交
826
				let command = byId[menu.command];
827 828

				if (!command) {
J
Joao Moreno 已提交
829
					command = { id: menu.command, title: '', keybindings: [], menus: [context] };
J
Joao Moreno 已提交
830
					byId[command.id] = command;
831 832 833 834 835 836 837
					commands.push(command);
				} else {
					command.menus.push(context);
				}
			});
		});

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

840
		rawKeybindings.forEach(rawKeybinding => {
S
Sandeep Somavarapu 已提交
841
			const keybinding = this.resolveKeybinding(rawKeybinding);
J
Joao Moreno 已提交
842

S
Sandeep Somavarapu 已提交
843
			if (!keybinding) {
J
Joao Moreno 已提交
844 845 846
				return;
			}

J
Joao Moreno 已提交
847
			let command = byId[rawKeybinding.command];
J
Joao Moreno 已提交
848 849

			if (!command) {
S
Sandeep Somavarapu 已提交
850
				command = { id: rawKeybinding.command, title: '', keybindings: [keybinding], menus: [] };
J
Joao Moreno 已提交
851
				byId[command.id] = command;
J
Joao Moreno 已提交
852 853
				commands.push(command);
			} else {
S
Sandeep Somavarapu 已提交
854
				command.keybindings.push(keybinding);
J
Joao Moreno 已提交
855 856 857
			}
		});

858
		if (!commands.length) {
859
			return false;
860 861
		}

S
Sandeep Somavarapu 已提交
862 863 864 865 866 867
		const renderKeybinding = (keybinding: ResolvedKeybinding): HTMLElement => {
			const element = $('');
			new KeybindingLabel(element, OS).set(keybinding, null);
			return element;
		};

J
Joao Moreno 已提交
868
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
869 870 871 872 873
			$('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 已提交
874
					$('th', null, localize('keyboard shortcuts', "Keyboard Shortcuts")),
875 876 877
					$('th', null, localize('menuContexts', "Menu Contexts"))
				),
				...commands.map(c => $('tr', null,
878
					$('td', null, $('code', null, c.id)),
879
					$('td', null, c.title),
S
Sandeep Somavarapu 已提交
880
					$('td', null, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))),
881 882 883
					$('td', null, ...c.menus.map(context => $('code', null, context)))
				))
			)
J
Joao Moreno 已提交
884 885 886
		);

		append(container, details);
887
		return true;
888 889
	}

B
Benjamin Pasero 已提交
890
	private renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
891 892
		const contributes = manifest.contributes;
		const rawLanguages = contributes && contributes.languages || [];
J
Joao Moreno 已提交
893 894
		const languages = rawLanguages.map(l => ({
			id: l.id,
J
Joao Moreno 已提交
895
			name: (l.aliases || [])[0] || l.id,
J
Joao Moreno 已提交
896 897 898
			extensions: l.extensions || [],
			hasGrammar: false,
			hasSnippets: false
J
Joao Moreno 已提交
899 900
		}));

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

J
Joao Moreno 已提交
903
		const grammars = contributes && contributes.grammars || [];
J
Joao Moreno 已提交
904 905 906 907 908

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

			if (!language) {
J
Joao Moreno 已提交
909
				language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false };
J
Joao Moreno 已提交
910 911 912 913 914 915 916
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasGrammar = true;
			}
		});

J
Joao Moreno 已提交
917 918 919 920 921 922 923 924 925 926 927 928 929 930
		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 已提交
931
		if (!languages.length) {
932
			return false;
J
Joao Moreno 已提交
933 934
		}

J
Joao Moreno 已提交
935
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
936 937 938
			$('summary', null, localize('languages', "Languages ({0})", languages.length)),
			$('table', null,
				$('tr', null,
J
Joao Moreno 已提交
939 940
					$('th', null, localize('language id', "ID")),
					$('th', null, localize('language name', "Name")),
J
Joao Moreno 已提交
941
					$('th', null, localize('file extensions', "File Extensions")),
J
Joao Moreno 已提交
942 943
					$('th', null, localize('grammar', "Grammar")),
					$('th', null, localize('snippets', "Snippets"))
J
Joao Moreno 已提交
944 945
				),
				...languages.map(l => $('tr', null,
J
Joao Moreno 已提交
946
					$('td', null, l.id),
J
Joao Moreno 已提交
947
					$('td', null, l.name),
J
Joao Moreno 已提交
948
					$('td', null, ...join(l.extensions.map(ext => $('code', null, ext)), ' ')),
J
Joao Moreno 已提交
949 950
					$('td', null, document.createTextNode(l.hasGrammar ? '✔︎' : '')),
					$('td', null, document.createTextNode(l.hasSnippets ? '✔︎' : ''))
J
Joao Moreno 已提交
951 952
				))
			)
J
Joao Moreno 已提交
953 954 955
		);

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

S
Sandeep Somavarapu 已提交
959
	private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding {
960 961
		let key: string;

J
Johannes Rieken 已提交
962
		switch (process.platform) {
963 964 965 966 967
			case 'win32': key = rawKeyBinding.win; break;
			case 'linux': key = rawKeyBinding.linux; break;
			case 'darwin': key = rawKeyBinding.mac; break;
		}

968
		const keyBinding = KeybindingIO.readKeybinding(key || rawKeyBinding.key, OS);
A
Alex Dima 已提交
969 970 971 972
		if (!keyBinding) {
			return null;
		}

S
Sandeep Somavarapu 已提交
973
		return this.keybindingService.resolveKeybinding(keyBinding)[0];
974 975
	}

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

J
Joao Moreno 已提交
979 980
		let promise = loadingTask();
		promise = always(promise, () => removeClass(this.content, 'loading'));
J
Joao Moreno 已提交
981

J
Joao Moreno 已提交
982
		this.contentDisposables.push(toDisposable(() => promise.cancel()));
J
Joao Moreno 已提交
983 984 985
	}

	layout(): void {
J
Joao Moreno 已提交
986
		this.layoutParticipants.forEach(p => p.layout());
J
Joao Moreno 已提交
987 988
	}

989 990 991 992 993
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

994
		this.notificationService.error(err);
995 996
	}

J
Joao Moreno 已提交
997
	dispose(): void {
J
Joao Moreno 已提交
998 999
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
1000 1001 1002
		super.dispose();
	}
}
1003

1004 1005 1006 1007 1008 1009 1010 1011 1012
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 {
1013
		const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor;
B
Benjamin Pasero 已提交
1014 1015
		if (activeControl instanceof ExtensionEditor) {
			return activeControl;
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
		}
		return null;
	}
}
const showCommand = new ShowExtensionEditorFindCommand({
	id: 'editor.action.extensioneditor.showfind',
	precondition: KEYBINDING_CONTEXT_EXTENSIONEDITOR_WEBVIEW_FOCUS,
	kbOpts: {
		primary: KeyMod.CtrlCmd | KeyCode.KEY_F
	}
});
KeybindingsRegistry.registerCommandAndKeybindingRule(showCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));