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';
J
Joao Moreno 已提交
41 42
import { Keybinding } from 'vs/base/common/keyCodes';
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';
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,
J
Joao Moreno 已提交
143
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
144 145
		@IKeybindingService private keybindingService: IKeybindingService,
		@IMessageService private messageService: IMessageService
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 242 243 244 245 246 247 248
		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 已提交
249 250
		this.extensionActionBar.clear();
		this.extensionActionBar.push([enableAction, updateAction, installAction], { icon: true, label: true });
251
		this.transientDisposables.push(enableAction, updateAction, installAction);
J
Joao Moreno 已提交
252

J
Joao Moreno 已提交
253 254
		this.navbar.clear();
		this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
255 256
		this.navbar.push(NavbarSection.Readme, localize('details', "Details"));
		this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"));
X
XVincentX 已提交
257
		this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"));
J
Joao Moreno 已提交
258 259 260 261 262 263 264 265 266

		this.content.innerHTML = '';

		return super.setInput(input, options);
	}

	private onNavbarChange(extension: IExtension, id: string): void {
		switch (id) {
			case NavbarSection.Readme: return this.openReadme(extension);
267
			case NavbarSection.Contributions: return this.openContributions(extension);
X
XVincentX 已提交
268
			case NavbarSection.Changelog: return this.openChangelog(extension);
J
Joao Moreno 已提交
269
		}
J
Joao Moreno 已提交
270
	}
J
Joao Moreno 已提交
271

X
XVincentX 已提交
272
	private openMarkdown(extension: IExtension, content: TPromise<string>, noContentCopy: string) {
273
		return this.loadContents(() => content
J
Joao Moreno 已提交
274
			.then(marked.parse)
J
Joao Moreno 已提交
275
			.then(renderBody)
J
Joao Moreno 已提交
276 277 278 279 280 281 282
			.then<void>(body => {
				const webview = new WebView(
					this.content,
					document.querySelector('.monaco-editor-background')
				);

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

J
Joao Moreno 已提交
285
				const linkListener = webview.onDidClickLink(link => shell.openExternal(link.toString(true)));
J
Joao Moreno 已提交
286 287 288
				const themeListener = this.themeService.onDidColorThemeChange(themeId => webview.style(themeId));
				this.contentDisposables.push(webview, linkListener, themeListener);
			})
J
Joao Moreno 已提交
289 290
			.then(null, () => {
				const p = append(this.content, $('p'));
X
XVincentX 已提交
291
				p.textContent = noContentCopy;
J
Joao Moreno 已提交
292
			}));
J
Joao Moreno 已提交
293
	}
J
Joao Moreno 已提交
294

295
	private openReadme(extension: IExtension) {
X
XVincentX 已提交
296
		return this.openMarkdown(extension, this.extensionReadme.get(), localize('noReadme', "No README available."));
297 298 299
	}

	private openChangelog(extension : IExtension) {
X
XVincentX 已提交
300
		return this.openMarkdown(extension, this.extensionChangelog.get(), localize('noChangelog', "No CHANGELOG available."));
301 302
	}

303
	private openContributions(extension: IExtension) {
J
Joao Moreno 已提交
304
		return this.loadContents(() => this.extensionManifest.get()
305
			.then(manifest => {
J
Joao Moreno 已提交
306
				this.content.innerHTML = '';
J
Joao Moreno 已提交
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324

				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();
325
			}));
J
Joao Moreno 已提交
326 327
	}

J
Joao Moreno 已提交
328
	private static renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): void {
J
Joao Moreno 已提交
329 330
		const contributes = manifest.contributes;
		const configuration = contributes && contributes.configuration;
J
Joao Moreno 已提交
331
		const properties = configuration && configuration.properties;
J
Joao Moreno 已提交
332
		const contrib = properties ? Object.keys(properties) : [];
J
Joao Moreno 已提交
333

J
Joao Moreno 已提交
334
		if (!contrib.length) {
J
Joao Moreno 已提交
335 336 337
			return;
		}

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

		append(container, details);
J
Joao Moreno 已提交
347 348
	}

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

J
Joao Moreno 已提交
353
		if (!contrib.length) {
J
Joao Moreno 已提交
354 355 356
			return;
		}

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

		append(container, details);
J
Joao Moreno 已提交
366 367
	}

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

J
Joao Moreno 已提交
372
		if (!contrib.length) {
J
Joao Moreno 已提交
373 374 375
			return;
		}

J
Joao Moreno 已提交
376
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
377
			$('summary', null, localize('themes', "Themes ({0})", contrib.length)),
J
Joao Moreno 已提交
378 379 380 381
			$('ul', null, ...contrib.map(theme => $('li', null, theme.label)))
		);

		append(container, details);
J
Joao Moreno 已提交
382 383
	}

J
Joao Moreno 已提交
384
	private static renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): void {
J
Joao Moreno 已提交
385 386
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
387 388 389 390 391

		if (!contrib.length) {
			return;
		}

J
Joao Moreno 已提交
392
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
393
			$('summary', null, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
J
Joao Moreno 已提交
394 395 396 397
			$('ul', null, ...contrib.map(v => $('li', null, v.fileMatch)))
		);

		append(container, details);
J
Joao Moreno 已提交
398 399
	}

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

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

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

414 415
		Object.keys(menus).forEach(context => {
			menus[context].forEach(menu => {
J
Joao Moreno 已提交
416
				let command = byId[menu.command];
417 418

				if (!command) {
J
Joao Moreno 已提交
419
					command = { id: menu.command, title: '', keybindings: [], menus: [context] };
J
Joao Moreno 已提交
420
					byId[command.id] = command;
421 422 423 424 425 426 427
					commands.push(command);
				} else {
					command.menus.push(context);
				}
			});
		});

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

430 431
		rawKeybindings.forEach(rawKeybinding => {
			const keyLabel = this.keybindingToLabel(rawKeybinding);
J
Joao Moreno 已提交
432
			let command = byId[rawKeybinding.command];
J
Joao Moreno 已提交
433 434

			if (!command) {
435
				command = { id: rawKeybinding.command, title: '', keybindings: [keyLabel], menus: [] };
J
Joao Moreno 已提交
436
				byId[command.id] = command;
J
Joao Moreno 已提交
437 438
				commands.push(command);
			} else {
439
				command.keybindings.push(keyLabel);
J
Joao Moreno 已提交
440 441 442
			}
		});

443 444 445 446
		if (!commands.length) {
			return;
		}

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

		append(container, details);
466 467
	}

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

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

J
Joao Moreno 已提交
481
		const grammars = contributes && contributes.grammars || [];
J
Joao Moreno 已提交
482 483 484 485 486

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

			if (!language) {
J
Joao Moreno 已提交
487
				language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false };
J
Joao Moreno 已提交
488 489 490 491 492 493 494
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasGrammar = true;
			}
		});

J
Joao Moreno 已提交
495 496 497 498 499 500 501 502 503 504 505 506 507 508
		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 已提交
509 510 511 512
		if (!languages.length) {
			return;
		}

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

		append(container, details);
J
Joao Moreno 已提交
532 533
	}

534 535 536 537 538 539 540 541 542 543 544 545 546
	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 已提交
547 548 549
	private loadContents(loadingTask: ()=>TPromise<any>): void {
		this.contentDisposables = dispose(this.contentDisposables);

J
Joao Moreno 已提交
550
		this.content.innerHTML = '';
J
Joao Moreno 已提交
551
		addClass(this.content, 'loading');
J
Joao Moreno 已提交
552

J
Joao Moreno 已提交
553 554
		let promise = loadingTask();
		promise = always(promise, () => removeClass(this.content, 'loading'));
J
Joao Moreno 已提交
555

J
Joao Moreno 已提交
556
		this.contentDisposables.push(toDisposable(() => promise.cancel()));
J
Joao Moreno 已提交
557 558 559
	}

	layout(): void {
J
Joao Moreno 已提交
560
		this.layoutParticipants.forEach(p => p.layout());
J
Joao Moreno 已提交
561 562
	}

563 564 565 566 567 568 569 570
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

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

J
Joao Moreno 已提交
571 572
	dispose(): void {
		this._highlight = null;
J
Joao Moreno 已提交
573 574
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
575 576 577
		super.dispose();
	}
}