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

'use strict';

J
Joao Moreno 已提交
8
import 'vs/css!./media/extensionEditor';
J
Joao Moreno 已提交
9
import { localize } from 'vs/nls';
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';
J
Joao Moreno 已提交
14
import Event, { Emitter, once, fromEventEmitter, chain } from 'vs/base/common/event';
J
Joao Moreno 已提交
15
import Cache from 'vs/base/common/cache';
J
Joao Moreno 已提交
16
import { Action } from 'vs/base/common/actions';
17
import { isPromiseCanceledError } from 'vs/base/common/errors';
18
import Severity from 'vs/base/common/severity';
J
Joao Moreno 已提交
19
import { IDisposable, empty, dispose, toDisposable } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
20
import { Builder } from 'vs/base/browser/builder';
21
import { domEvent } from 'vs/base/browser/event';
22
import { append, $, addClass, removeClass, finalHandler, join } from 'vs/base/browser/dom';
J
Joao Moreno 已提交
23
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
J
Joao Moreno 已提交
24
import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService';
J
Joao Moreno 已提交
25 26
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
27
import { IExtensionGalleryService, IExtensionManifest, IKeyBinding } from 'vs/platform/extensionManagement/common/extensionManagement';
J
Joao Moreno 已提交
28
import { IThemeService } from 'vs/workbench/services/themes/common/themeService';
29
import { ExtensionsInput } from './extensionsInput';
J
Joao Moreno 已提交
30
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension } from './extensions';
31
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
J
Joao Moreno 已提交
32
import { ITemplateData } from './extensionsList';
33
import { RatingsWidget, InstallWidget } from './extensionsWidgets';
J
Joao Moreno 已提交
34
import { EditorOptions } from 'vs/workbench/common/editor';
J
Joao Moreno 已提交
35 36
import { shell } from 'electron';
import product from 'vs/platform/product';
J
Joao Moreno 已提交
37
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
38
import { CombinedInstallAction, UpdateAction, EnableAction } from './extensionsActions';
J
Joao Moreno 已提交
39
import WebView from 'vs/workbench/parts/html/browser/webview';
40
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
A
Alexandru Dima 已提交
41
import { Keybinding } from 'vs/base/common/keybinding';
J
Joao Moreno 已提交
42
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
J
Joao Moreno 已提交
43
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
44
import { IMessageService } from 'vs/platform/message/common/message';
45
import { IOpenerService } from 'vs/platform/opener/common/opener';
J
Joao Moreno 已提交
46 47 48 49 50 51 52 53 54 55 56

function renderBody(body: string): string {
	return `<!DOCTYPE html>
		<html>
			<head>
				<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
				<link rel="stylesheet" type="text/css" href="${ require.toUrl('./media/markdown.css') }" >
			</head>
			<body>${ body }</body>
		</html>`;
}
J
Joao Moreno 已提交
57

J
Joao Moreno 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
class NavBar {

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

	private actions: Action[];
	private actionbar: ActionBar;

	constructor(container: HTMLElement) {
		const element = append(container, $('.navbar'));
		this.actions = [];
		this.actionbar = new ActionBar(element, { animated: false });
	}

	push(id: string, label: string): void {
		const run = () => {
			this._onChange.fire(id);
			this.actions.forEach(a => a.enabled = a.id !== action.id);
			return TPromise.as(null);
		};

		const action = new Action(id, label, null, true, run);

		this.actions.push(action);
		this.actionbar.push(action);

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

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

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

const NavbarSection = {
	Readme: 'readme',
X
XVincentX 已提交
101 102
	Contributions: 'contributions',
	Changelog: 'changelog'
J
Joao Moreno 已提交
103 104
};

J
Joao Moreno 已提交
105 106 107 108
interface ILayoutParticipant {
	layout(): void;
}

J
Joao Moreno 已提交
109 110 111 112
export class ExtensionEditor extends BaseEditor {

	static ID: string = 'workbench.editor.extension';

113
	private icon: HTMLImageElement;
J
Joao Moreno 已提交
114 115 116
	private name: HTMLElement;
	private license: HTMLElement;
	private publisher: HTMLElement;
J
Joao Moreno 已提交
117
	private installCount: HTMLElement;
J
Joao Moreno 已提交
118
	private rating: HTMLElement;
J
Joao Moreno 已提交
119
	private description: HTMLElement;
J
Joao Moreno 已提交
120 121 122
	private extensionActionBar: ActionBar;
	private navbar: NavBar;
	private content: HTMLElement;
J
Joao Moreno 已提交
123 124 125 126

	private _highlight: ITemplateData;
	private highlightDisposable: IDisposable;

J
Joao Moreno 已提交
127
	private extensionReadme: Cache<string>;
X
XVincentX 已提交
128
	private extensionChangelog: Cache<string>;
J
Joao Moreno 已提交
129 130
	private extensionManifest: Cache<IExtensionManifest>;

J
Joao Moreno 已提交
131
	private layoutParticipants: ILayoutParticipant[] = [];
J
Joao Moreno 已提交
132 133
	private contentDisposables: IDisposable[] = [];
	private transientDisposables: IDisposable[] = [];
J
Joao Moreno 已提交
134
	private disposables: IDisposable[];
J
Joao Moreno 已提交
135 136 137

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
138 139
		@IExtensionGalleryService private galleryService: IExtensionGalleryService,
		@IConfigurationService private configurationService: IConfigurationService,
J
Joao Moreno 已提交
140
		@IInstantiationService private instantiationService: IInstantiationService,
141
		@IViewletService private viewletService: IViewletService,
J
Joao Moreno 已提交
142
		@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
143
		@IThemeService private themeService: IThemeService,
J
Joao Moreno 已提交
144
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
145
		@IKeybindingService private keybindingService: IKeybindingService,
146 147
		@IMessageService private messageService: IMessageService,
		@IOpenerService private openerService: IOpenerService
J
Joao Moreno 已提交
148 149 150 151
	) {
		super(ExtensionEditor.ID, telemetryService);
		this._highlight = null;
		this.highlightDisposable = empty;
J
Joao Moreno 已提交
152
		this.disposables = [];
J
Joao Moreno 已提交
153
		this.extensionReadme = null;
X
XVincentX 已提交
154
		this.extensionChangelog = null;
J
Joao Moreno 已提交
155
		this.extensionManifest = null;
J
Joao Moreno 已提交
156 157 158 159
	}

	createEditor(parent: Builder): void {
		const container = parent.getHTMLElement();
J
Joao Moreno 已提交
160 161 162

		const root = append(container, $('.extension-editor'));
		const header = append(root, $('.header'));
J
Joao Moreno 已提交
163

164
		this.icon = append(header, $<HTMLImageElement>('img.icon'));
J
Joao Moreno 已提交
165

J
Joao Moreno 已提交
166
		const details = append(header, $('.details'));
J
Joao Moreno 已提交
167
		const title = append(details, $('.title'));
J
Joao Moreno 已提交
168
		this.name = append(title, $('span.name.clickable'));
J
Joao Moreno 已提交
169 170

		const subtitle = append(details, $('.subtitle'));
J
Joao Moreno 已提交
171
		this.publisher = append(subtitle, $('span.publisher.clickable'));
J
Joao Moreno 已提交
172

173
		this.installCount = append(subtitle, $('span.install'));
J
Joao Moreno 已提交
174

J
Joao Moreno 已提交
175
		this.rating = append(subtitle, $('span.rating.clickable'));
J
Joao Moreno 已提交
176

J
Joao Moreno 已提交
177
		this.license = append(subtitle, $('span.license.clickable'));
J
Joao Moreno 已提交
178
		this.license.textContent = localize('license', 'License');
179
		this.license.style.display = 'none';
J
Joao Moreno 已提交
180

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

J
Joao Moreno 已提交
183 184 185 186
		const extensionActions = append(details, $('.actions'));
		this.extensionActionBar = new ActionBar(extensionActions, { animated: false });
		this.disposables.push(this.extensionActionBar);

J
Joao Moreno 已提交
187 188 189 190
		chain(fromEventEmitter<{ error?: any; }>(this.extensionActionBar, 'run'))
			.map(({ error }) => error)
			.filter(error => !!error)
			.on(this.onError, this, this.disposables);
191

J
Joao Moreno 已提交
192 193
		const body = append(root, $('.body'));
		this.navbar = new NavBar(body);
J
Joao Moreno 已提交
194

J
Joao Moreno 已提交
195
		this.content = append(body, $('.content'));
J
Joao Moreno 已提交
196 197 198
	}

	setInput(input: ExtensionsInput, options: EditorOptions): TPromise<void> {
J
Joao Moreno 已提交
199 200
		const extension = input.extension;

J
Joao Moreno 已提交
201 202
		this.transientDisposables = dispose(this.transientDisposables);

203
		this.telemetryService.publicLog('extensionGallery:openExtension', extension.telemetryData);
J
Joao Moreno 已提交
204

J
Joao Moreno 已提交
205
		this.extensionReadme = new Cache(() => extension.getReadme());
X
XVincentX 已提交
206
		this.extensionChangelog = new Cache(() => extension.getChangelog());
J
Joao Moreno 已提交
207 208
		this.extensionManifest = new Cache(() => extension.getManifest());

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

J
Joao Moreno 已提交
213 214 215
		this.name.textContent = extension.displayName;
		this.publisher.textContent = extension.publisherDisplayName;
		this.description.textContent = extension.description;
J
Joao Moreno 已提交
216

J
Joao Moreno 已提交
217 218 219
		if (product.extensionsGallery) {
			const extensionUrl = `${ product.extensionsGallery.itemUrl }?itemName=${ extension.publisher }.${ extension.name }`;

J
Joao Moreno 已提交
220 221 222
			this.name.onclick = finalHandler(() => shell.openExternal(extensionUrl));
			this.rating.onclick = finalHandler(() => shell.openExternal(`${ extensionUrl }#review-details`));
			this.publisher.onclick = finalHandler(() => {
223
				this.viewletService.openViewlet(VIEWLET_ID, true)
J
Joao Moreno 已提交
224
					.then(viewlet => viewlet as IExtensionsViewlet)
225
					.done(viewlet => viewlet.search(`publisher:"${ extension.publisherDisplayName }"`));
J
Joao Moreno 已提交
226
			});
227 228 229 230 231 232 233 234

			if (extension.licenseUrl) {
				this.license.onclick = finalHandler(() => shell.openExternal(extension.licenseUrl));
				this.license.style.display = 'initial';
			} else {
				this.license.onclick = null;
				this.license.style.display = 'none';
			}
J
Joao Moreno 已提交
235 236
		}

J
Joao Moreno 已提交
237
		const install = this.instantiationService.createInstance(InstallWidget, this.installCount, { extension });
J
Joao Moreno 已提交
238 239
		this.transientDisposables.push(install);

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

J
Joao Moreno 已提交
243 244 245 246 247 248 249 250
		const installAction = this.instantiationService.createInstance(CombinedInstallAction);
		const updateAction = this.instantiationService.createInstance(UpdateAction);
		const enableAction = this.instantiationService.createInstance(EnableAction);

		installAction.extension = extension;
		updateAction.extension = extension;
		enableAction.extension = extension;

J
Joao Moreno 已提交
251 252
		this.extensionActionBar.clear();
		this.extensionActionBar.push([enableAction, updateAction, installAction], { icon: true, label: true });
253
		this.transientDisposables.push(enableAction, updateAction, installAction);
J
Joao Moreno 已提交
254

J
Joao Moreno 已提交
255 256
		this.navbar.clear();
		this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
257 258
		this.navbar.push(NavbarSection.Readme, localize('details', "Details"));
		this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"));
J
Joao Moreno 已提交
259

260 261 262
		if (extension.hasChangelog) {
			this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"));
		}
J
Joao Moreno 已提交
263 264 265 266 267 268 269 270

		this.content.innerHTML = '';

		return super.setInput(input, options);
	}

	private onNavbarChange(extension: IExtension, id: string): void {
		switch (id) {
271 272 273
			case NavbarSection.Readme: return this.openReadme();
			case NavbarSection.Contributions: return this.openContributions();
			case NavbarSection.Changelog: return this.openChangelog();
J
Joao Moreno 已提交
274
		}
J
Joao Moreno 已提交
275
	}
J
Joao Moreno 已提交
276

277
	private openMarkdown(content: TPromise<string>, noContentCopy: string) {
278
		return this.loadContents(() => content
J
Joao Moreno 已提交
279
			.then(marked.parse)
J
Joao Moreno 已提交
280
			.then(renderBody)
J
Joao Moreno 已提交
281 282 283 284 285 286 287
			.then<void>(body => {
				const webview = new WebView(
					this.content,
					document.querySelector('.monaco-editor-background')
				);

				webview.style(this.themeService.getColorTheme());
J
Joao Moreno 已提交
288
				webview.contents = [body];
J
Joao Moreno 已提交
289

290 291 292
				webview.onDidClickLink(link => this.openerService.open(link), null, this.contentDisposables);
				this.themeService.onDidColorThemeChange(themeId => webview.style(themeId), null, this.contentDisposables);
				this.contentDisposables.push(webview);
J
Joao Moreno 已提交
293
			})
J
Joao Moreno 已提交
294 295
			.then(null, () => {
				const p = append(this.content, $('p'));
X
XVincentX 已提交
296
				p.textContent = noContentCopy;
J
Joao Moreno 已提交
297
			}));
J
Joao Moreno 已提交
298
	}
J
Joao Moreno 已提交
299

300 301
	private openReadme() {
		return this.openMarkdown(this.extensionReadme.get(), localize('noReadme', "No README available."));
302 303
	}

304 305
	private openChangelog() {
		return this.openMarkdown(this.extensionChangelog.get(), localize('noChangelog', "No CHANGELOG available."));
306 307
	}

308
	private openContributions() {
J
Joao Moreno 已提交
309
		return this.loadContents(() => this.extensionManifest.get()
310
			.then(manifest => {
J
Joao Moreno 已提交
311
				this.content.innerHTML = '';
J
Joao Moreno 已提交
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329

				const content = $('div', { class: 'subcontent' });
				const scrollableContent = new DomScrollableElement(content, { canUseTranslate3d: false });
				append(this.content, scrollableContent.getDomNode());
				this.contentDisposables.push(scrollableContent);

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

				ExtensionEditor.renderSettings(content, manifest, layout);
				this.renderCommands(content, manifest, layout);
				ExtensionEditor.renderLanguages(content, manifest, layout);
				ExtensionEditor.renderThemes(content, manifest, layout);
				ExtensionEditor.renderJSONValidation(content, manifest, layout);
				ExtensionEditor.renderDebuggers(content, manifest, layout);

				scrollableContent.scanDomNode();
330
			}));
J
Joao Moreno 已提交
331 332
	}

J
Joao Moreno 已提交
333
	private static renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): void {
J
Joao Moreno 已提交
334 335
		const contributes = manifest.contributes;
		const configuration = contributes && contributes.configuration;
J
Joao Moreno 已提交
336
		const properties = configuration && configuration.properties;
J
Joao Moreno 已提交
337
		const contrib = properties ? Object.keys(properties) : [];
J
Joao Moreno 已提交
338

J
Joao Moreno 已提交
339
		if (!contrib.length) {
J
Joao Moreno 已提交
340 341 342
			return;
		}

J
Joao Moreno 已提交
343
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
344
			$('summary', null, localize('settings', "Settings ({0})", contrib.length)),
J
Joao Moreno 已提交
345
			$('table', null,
J
Joao Moreno 已提交
346
				$('tr', null, $('th', null, localize('setting name', "Name")), $('th', null, localize('description', "Description"))),
J
Joao Moreno 已提交
347
				...contrib.map(key => $('tr', null, $('td', null, $('code', null, key)), $('td', null, properties[key].description)))
J
Joao Moreno 已提交
348
			)
J
Joao Moreno 已提交
349 350 351
		);

		append(container, details);
J
Joao Moreno 已提交
352 353
	}

J
Joao Moreno 已提交
354
	private static renderDebuggers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): void {
J
Joao Moreno 已提交
355 356
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.debuggers || [];
J
Joao Moreno 已提交
357

J
Joao Moreno 已提交
358
		if (!contrib.length) {
J
Joao Moreno 已提交
359 360 361
			return;
		}

J
Joao Moreno 已提交
362
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
363
			$('summary', null, localize('debuggers', "Debuggers ({0})", contrib.length)),
J
Joao Moreno 已提交
364
			$('table', null,
J
Joao Moreno 已提交
365
				$('tr', null, $('th', null, localize('debugger name', "Name")), $('th', null, localize('runtime', "Runtime"))),
J
Joao Moreno 已提交
366
				...contrib.map(d => $('tr', null, $('td', null, d.label || d.type), $('td', null, d.runtime)))
J
Joao Moreno 已提交
367
			)
J
Joao Moreno 已提交
368 369 370
		);

		append(container, details);
J
Joao Moreno 已提交
371 372
	}

J
Joao Moreno 已提交
373
	private static renderThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): void {
J
Joao Moreno 已提交
374 375
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.themes || [];
J
Joao Moreno 已提交
376

J
Joao Moreno 已提交
377
		if (!contrib.length) {
J
Joao Moreno 已提交
378 379 380
			return;
		}

J
Joao Moreno 已提交
381
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
382
			$('summary', null, localize('themes', "Themes ({0})", contrib.length)),
J
Joao Moreno 已提交
383 384 385 386
			$('ul', null, ...contrib.map(theme => $('li', null, theme.label)))
		);

		append(container, details);
J
Joao Moreno 已提交
387 388
	}

J
Joao Moreno 已提交
389
	private static renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): void {
J
Joao Moreno 已提交
390 391
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
392 393 394 395 396

		if (!contrib.length) {
			return;
		}

J
Joao Moreno 已提交
397
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
398
			$('summary', null, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
J
Joao Moreno 已提交
399 400 401 402
			$('ul', null, ...contrib.map(v => $('li', null, v.fileMatch)))
		);

		append(container, details);
J
Joao Moreno 已提交
403 404
	}

J
Joao Moreno 已提交
405
	private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): void {
J
Joao Moreno 已提交
406 407
		const contributes = manifest.contributes;
		const rawCommands = contributes && contributes.commands || [];
J
Joao Moreno 已提交
408
		const commands = rawCommands.map(c => ({
409 410
			id: c.command,
			title: c.title,
J
Joao Moreno 已提交
411
			keybindings: [],
412 413 414
			menus: []
		}));

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

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

419 420
		Object.keys(menus).forEach(context => {
			menus[context].forEach(menu => {
J
Joao Moreno 已提交
421
				let command = byId[menu.command];
422 423

				if (!command) {
J
Joao Moreno 已提交
424
					command = { id: menu.command, title: '', keybindings: [], menus: [context] };
J
Joao Moreno 已提交
425
					byId[command.id] = command;
426 427 428 429 430 431 432
					commands.push(command);
				} else {
					command.menus.push(context);
				}
			});
		});

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

435 436
		rawKeybindings.forEach(rawKeybinding => {
			const keyLabel = this.keybindingToLabel(rawKeybinding);
J
Joao Moreno 已提交
437
			let command = byId[rawKeybinding.command];
J
Joao Moreno 已提交
438 439

			if (!command) {
440
				command = { id: rawKeybinding.command, title: '', keybindings: [keyLabel], menus: [] };
J
Joao Moreno 已提交
441
				byId[command.id] = command;
J
Joao Moreno 已提交
442 443
				commands.push(command);
			} else {
444
				command.keybindings.push(keyLabel);
J
Joao Moreno 已提交
445 446 447
			}
		});

448 449 450 451
		if (!commands.length) {
			return;
		}

J
Joao Moreno 已提交
452
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
453 454 455 456 457
			$('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 已提交
458
					$('th', null, localize('keyboard shortcuts', "Keyboard Shortcuts")),
459 460 461
					$('th', null, localize('menuContexts', "Menu Contexts"))
				),
				...commands.map(c => $('tr', null,
462
					$('td', null, $('code', null, c.id)),
463
					$('td', null, c.title),
464
					$('td', null, ...join(c.keybindings.map(keybinding => $('code', null, keybinding)), ' ')),
465 466 467
					$('td', null, ...c.menus.map(context => $('code', null, context)))
				))
			)
J
Joao Moreno 已提交
468 469 470
		);

		append(container, details);
471 472
	}

J
Joao Moreno 已提交
473
	private static renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): void {
J
Joao Moreno 已提交
474 475
		const contributes = manifest.contributes;
		const rawLanguages = contributes && contributes.languages || [];
J
Joao Moreno 已提交
476 477
		const languages = rawLanguages.map(l => ({
			id: l.id,
J
Joao Moreno 已提交
478
			name: (l.aliases || [])[0] || l.id,
J
Joao Moreno 已提交
479 480 481
			extensions: l.extensions || [],
			hasGrammar: false,
			hasSnippets: false
J
Joao Moreno 已提交
482 483
		}));

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

J
Joao Moreno 已提交
486
		const grammars = contributes && contributes.grammars || [];
J
Joao Moreno 已提交
487 488 489 490 491

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

			if (!language) {
J
Joao Moreno 已提交
492
				language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false };
J
Joao Moreno 已提交
493 494 495 496 497 498 499
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasGrammar = true;
			}
		});

J
Joao Moreno 已提交
500 501 502 503 504 505 506 507 508 509 510 511 512 513
		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 已提交
514 515 516 517
		if (!languages.length) {
			return;
		}

J
Joao Moreno 已提交
518
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
519 520 521 522
			$('summary', null, localize('languages', "Languages ({0})", languages.length)),
			$('table', null,
				$('tr', null,
					$('th', null, localize('command name', "Name")),
J
Joao Moreno 已提交
523
					$('th', null, localize('file extensions', "File Extensions")),
J
Joao Moreno 已提交
524 525
					$('th', null, localize('grammar', "Grammar")),
					$('th', null, localize('snippets', "Snippets"))
J
Joao Moreno 已提交
526 527 528
				),
				...languages.map(l => $('tr', null,
					$('td', null, l.name),
J
Joao Moreno 已提交
529
					$('td', null, ...join(l.extensions.map(ext => $('code', null, ext)), ' ')),
J
Joao Moreno 已提交
530 531
					$('td', null, document.createTextNode(l.hasGrammar ? '✔︎' : '')),
					$('td', null, document.createTextNode(l.hasSnippets ? '✔︎' : ''))
J
Joao Moreno 已提交
532 533
				))
			)
J
Joao Moreno 已提交
534 535 536
		);

		append(container, details);
J
Joao Moreno 已提交
537 538
	}

539 540 541 542 543 544 545 546 547 548 549 550 551
	private keybindingToLabel(rawKeyBinding: IKeyBinding): string {
		let key: string;

		switch(process.platform) {
			case 'win32': key = rawKeyBinding.win; break;
			case 'linux': key = rawKeyBinding.linux; break;
			case 'darwin': key = rawKeyBinding.mac; break;
		}

		const keyBinding = new Keybinding(Keybinding.fromUserSettingsLabel(key || rawKeyBinding.key));
		return this.keybindingService.getLabelFor(keyBinding);
	}

J
Joao Moreno 已提交
552 553 554
	private loadContents(loadingTask: ()=>TPromise<any>): void {
		this.contentDisposables = dispose(this.contentDisposables);

J
Joao Moreno 已提交
555
		this.content.innerHTML = '';
J
Joao Moreno 已提交
556
		addClass(this.content, 'loading');
J
Joao Moreno 已提交
557

J
Joao Moreno 已提交
558 559
		let promise = loadingTask();
		promise = always(promise, () => removeClass(this.content, 'loading'));
J
Joao Moreno 已提交
560

J
Joao Moreno 已提交
561
		this.contentDisposables.push(toDisposable(() => promise.cancel()));
J
Joao Moreno 已提交
562 563 564
	}

	layout(): void {
J
Joao Moreno 已提交
565
		this.layoutParticipants.forEach(p => p.layout());
J
Joao Moreno 已提交
566 567
	}

568 569 570 571 572 573 574 575
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

		this.messageService.show(Severity.Error, err);
	}

J
Joao Moreno 已提交
576 577
	dispose(): void {
		this._highlight = null;
J
Joao Moreno 已提交
578 579
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
580 581 582
		super.dispose();
	}
}