extensionEditor.ts 20.5 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';
J
Joao Moreno 已提交
38
import { CombinedInstallAction, UpdateAction, EnableAction, BuiltinStatusLabelAction } from './extensionsActions';
J
Joao Moreno 已提交
39
import WebView from 'vs/workbench/parts/html/browser/webview';
A
Alexandru Dima 已提交
40
import { Keybinding } from 'vs/base/common/keybinding';
J
Joao Moreno 已提交
41
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
J
Joao Moreno 已提交
42
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
43
import { IMessageService } from 'vs/platform/message/common/message';
44
import { IOpenerService } from 'vs/platform/opener/common/opener';
J
Joao Moreno 已提交
45 46 47 48 49 50 51 52 53 54 55

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 已提交
56

J
Joao Moreno 已提交
57 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
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 已提交
100 101
	Contributions: 'contributions',
	Changelog: 'changelog'
J
Joao Moreno 已提交
102 103
};

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

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

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

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

	private _highlight: ITemplateData;
	private highlightDisposable: IDisposable;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
199 200
		this.transientDisposables = dispose(this.transientDisposables);

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

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

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

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

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

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

			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 已提交
233 234
		}

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

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

J
Joao Moreno 已提交
241
		const builtinStatusAction = this.instantiationService.createInstance(BuiltinStatusLabelAction);
J
Joao Moreno 已提交
242 243 244 245 246
		const installAction = this.instantiationService.createInstance(CombinedInstallAction);
		const updateAction = this.instantiationService.createInstance(UpdateAction);
		const enableAction = this.instantiationService.createInstance(EnableAction);

		installAction.extension = extension;
J
Joao Moreno 已提交
247
		builtinStatusAction.extension = extension;
J
Joao Moreno 已提交
248 249 250
		updateAction.extension = extension;
		enableAction.extension = extension;

J
Joao Moreno 已提交
251
		this.extensionActionBar.clear();
J
Joao Moreno 已提交
252 253
		this.extensionActionBar.push([enableAction, updateAction, installAction, builtinStatusAction], { icon: true, label: true });
		this.transientDisposables.push(enableAction, updateAction, installAction, builtinStatusAction);
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();
	}
}