extensionEditor.ts 24.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';
30
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies } from '../common/extensions';
31
import { Renderer, DataSource, Controller } from './dependenciesViewer';
32
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
J
Joao Moreno 已提交
33
import { ITemplateData } from './extensionsList';
34
import { RatingsWidget, InstallWidget } from './extensionsWidgets';
J
Joao Moreno 已提交
35
import { EditorOptions } from 'vs/workbench/common/editor';
J
Joao Moreno 已提交
36
import product from 'vs/platform/product';
J
Joao Moreno 已提交
37
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
38
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, 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
Johannes Rieken 已提交
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';
S
Sandeep Somavarapu 已提交
45
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
J
Joao Moreno 已提交
46 47 48 49 50 51

function renderBody(body: string): string {
	return `<!DOCTYPE html>
		<html>
			<head>
				<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
J
Johannes Rieken 已提交
52
				<link rel="stylesheet" type="text/css" href="${ require.toUrl('./media/markdown.css')}" >
J
Joao Moreno 已提交
53
			</head>
J
Johannes Rieken 已提交
54
			<body>${ body}</body>
J
Joao Moreno 已提交
55 56
		</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
	Contributions: 'contributions',
S
Sandeep Somavarapu 已提交
102 103
	Changelog: 'changelog',
	Dependencies: 'dependencies'
J
Joao Moreno 已提交
104 105
};

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

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

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

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

	private _highlight: ITemplateData;
	private highlightDisposable: IDisposable;

J
Joao Moreno 已提交
129
	private extensionReadme: Cache<string>;
X
XVincentX 已提交
130
	private extensionChangelog: Cache<string>;
J
Joao Moreno 已提交
131
	private extensionManifest: Cache<IExtensionManifest>;
S
Sandeep Somavarapu 已提交
132
	private extensionDependencies: Cache<IExtensionDependencies>;
J
Joao Moreno 已提交
133

J
Joao Moreno 已提交
134
	private layoutParticipants: ILayoutParticipant[] = [];
J
Joao Moreno 已提交
135 136
	private contentDisposables: IDisposable[] = [];
	private transientDisposables: IDisposable[] = [];
J
Joao Moreno 已提交
137
	private disposables: IDisposable[];
J
Joao Moreno 已提交
138 139 140

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

	createEditor(parent: Builder): void {
		const container = parent.getHTMLElement();
J
Joao Moreno 已提交
163 164 165

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

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

J
Joao Moreno 已提交
169
		const details = append(header, $('.details'));
J
Joao Moreno 已提交
170
		const title = append(details, $('.title'));
J
Joao Moreno 已提交
171
		this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") }));
J
Joao Moreno 已提交
172
		this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") }));
J
Joao Moreno 已提交
173 174

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

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

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

J
Joao Moreno 已提交
181
		this.license = append(subtitle, $('span.license.clickable'));
J
Joao Moreno 已提交
182
		this.license.textContent = localize('license', 'License');
183
		this.license.style.display = 'none';
J
Joao Moreno 已提交
184

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

J
Joao Moreno 已提交
187
		const extensionActions = append(details, $('.actions'));
188 189 190 191 192 193 194 195 196 197 198 199
		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;
				}
				return null;
			}
		});
J
Joao Moreno 已提交
200 201
		this.disposables.push(this.extensionActionBar);

J
Joao Moreno 已提交
202 203 204 205
		chain(fromEventEmitter<{ error?: any; }>(this.extensionActionBar, 'run'))
			.map(({ error }) => error)
			.filter(error => !!error)
			.on(this.onError, this, this.disposables);
206

J
Joao Moreno 已提交
207 208
		const body = append(root, $('.body'));
		this.navbar = new NavBar(body);
J
Joao Moreno 已提交
209

J
Joao Moreno 已提交
210
		this.content = append(body, $('.content'));
J
Joao Moreno 已提交
211 212 213
	}

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

J
Joao Moreno 已提交
216 217
		this.transientDisposables = dispose(this.transientDisposables);

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

J
Joao Moreno 已提交
220
		this.extensionReadme = new Cache(() => extension.getReadme());
X
XVincentX 已提交
221
		this.extensionChangelog = new Cache(() => extension.getChangelog());
J
Joao Moreno 已提交
222
		this.extensionManifest = new Cache(() => extension.getManifest());
S
Sandeep Somavarapu 已提交
223
		this.extensionDependencies = new Cache(() => this.extensionsWorkbenchService.loadDependencies(extension));
J
Joao Moreno 已提交
224

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

J
Joao Moreno 已提交
229
		this.name.textContent = extension.displayName;
S
Sandeep Somavarapu 已提交
230
		this.identifier.textContent = `${extension.publisher}.${extension.name}`;
231

J
Joao Moreno 已提交
232 233
		this.publisher.textContent = extension.publisherDisplayName;
		this.description.textContent = extension.description;
J
Joao Moreno 已提交
234

J
Joao Moreno 已提交
235
		if (product.extensionsGallery) {
J
Johannes Rieken 已提交
236
			const extensionUrl = `${product.extensionsGallery.itemUrl}?itemName=${extension.publisher}.${extension.name}`;
J
Joao Moreno 已提交
237

B
Benjamin Pasero 已提交
238 239
			this.name.onclick = finalHandler(() => window.open(extensionUrl));
			this.rating.onclick = finalHandler(() => window.open(`${extensionUrl}#review-details`));
J
Joao Moreno 已提交
240
			this.publisher.onclick = finalHandler(() => {
241
				this.viewletService.openViewlet(VIEWLET_ID, true)
J
Joao Moreno 已提交
242
					.then(viewlet => viewlet as IExtensionsViewlet)
J
Johannes Rieken 已提交
243
					.done(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`));
J
Joao Moreno 已提交
244
			});
245 246

			if (extension.licenseUrl) {
B
Benjamin Pasero 已提交
247
				this.license.onclick = finalHandler(() => window.open(extension.licenseUrl));
248 249 250 251 252
				this.license.style.display = 'initial';
			} else {
				this.license.onclick = null;
				this.license.style.display = 'none';
			}
J
Joao Moreno 已提交
253 254
		}

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

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

J
Joao Moreno 已提交
261
		const builtinStatusAction = this.instantiationService.createInstance(BuiltinStatusLabelAction);
J
Joao Moreno 已提交
262 263 264
		const installAction = this.instantiationService.createInstance(CombinedInstallAction);
		const updateAction = this.instantiationService.createInstance(UpdateAction);
		const enableAction = this.instantiationService.createInstance(EnableAction);
265
		const disableAction = this.instantiationService.createInstance(DisableAction);
266
		const reloadAction = this.instantiationService.createInstance(ReloadAction);
J
Joao Moreno 已提交
267 268

		installAction.extension = extension;
J
Joao Moreno 已提交
269
		builtinStatusAction.extension = extension;
J
Joao Moreno 已提交
270 271
		updateAction.extension = extension;
		enableAction.extension = extension;
272
		disableAction.extension = extension;
273
		reloadAction.extension = extension;
J
Joao Moreno 已提交
274

J
Joao Moreno 已提交
275
		this.extensionActionBar.clear();
276 277
		this.extensionActionBar.push([enableAction, updateAction, reloadAction, disableAction, installAction, builtinStatusAction], { icon: true, label: true });
		this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, builtinStatusAction);
J
Joao Moreno 已提交
278

J
Joao Moreno 已提交
279 280
		this.navbar.clear();
		this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
281 282
		this.navbar.push(NavbarSection.Readme, localize('details', "Details"));
		this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"));
283 284
		this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"));
		this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"));
J
Joao Moreno 已提交
285 286 287 288 289 290 291

		this.content.innerHTML = '';

		return super.setInput(input, options);
	}

	private onNavbarChange(extension: IExtension, id: string): void {
S
Sandeep Somavarapu 已提交
292 293
		this.contentDisposables = dispose(this.contentDisposables);
		this.content.innerHTML = '';
J
Joao Moreno 已提交
294
		switch (id) {
295 296 297
			case NavbarSection.Readme: return this.openReadme();
			case NavbarSection.Contributions: return this.openContributions();
			case NavbarSection.Changelog: return this.openChangelog();
298
			case NavbarSection.Dependencies: return this.openDependencies(extension);
J
Joao Moreno 已提交
299
		}
J
Joao Moreno 已提交
300
	}
J
Joao Moreno 已提交
301

302
	private openMarkdown(content: TPromise<string>, noContentCopy: string) {
303
		return this.loadContents(() => content
J
Joao Moreno 已提交
304
			.then(marked.parse)
J
Joao Moreno 已提交
305
			.then(renderBody)
J
Joao Moreno 已提交
306 307 308 309 310 311 312
			.then<void>(body => {
				const webview = new WebView(
					this.content,
					document.querySelector('.monaco-editor-background')
				);

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

315 316 317
				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 已提交
318
			})
J
Joao Moreno 已提交
319
			.then(null, () => {
320
				const p = append(this.content, $('p.nocontent'));
X
XVincentX 已提交
321
				p.textContent = noContentCopy;
J
Joao Moreno 已提交
322
			}));
J
Joao Moreno 已提交
323
	}
J
Joao Moreno 已提交
324

325 326
	private openReadme() {
		return this.openMarkdown(this.extensionReadme.get(), localize('noReadme', "No README available."));
327 328
	}

329
	private openChangelog() {
330
		return this.openMarkdown(this.extensionChangelog.get(), localize('noChangelog', "No Changelog available."));
331 332
	}

333
	private openContributions() {
J
Joao Moreno 已提交
334
		return this.loadContents(() => this.extensionManifest.get()
335
			.then(manifest => {
J
Joao Moreno 已提交
336 337 338 339 340 341 342
				const content = $('div', { class: 'subcontent' });
				const scrollableContent = new DomScrollableElement(content, { canUseTranslate3d: false });

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

343 344 345 346 347 348 349
				let isEmpty = true;
				isEmpty = isEmpty && !ExtensionEditor.renderSettings(content, manifest, layout);
				isEmpty = isEmpty && !this.renderCommands(content, manifest, layout);
				isEmpty = isEmpty && !ExtensionEditor.renderLanguages(content, manifest, layout);
				isEmpty = isEmpty && !ExtensionEditor.renderThemes(content, manifest, layout);
				isEmpty = isEmpty && !ExtensionEditor.renderJSONValidation(content, manifest, layout);
				isEmpty = isEmpty && !ExtensionEditor.renderDebuggers(content, manifest, layout);
J
Joao Moreno 已提交
350 351

				scrollableContent.scanDomNode();
352 353 354 355 356 357 358 359

				if (isEmpty) {
					append(this.content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
					return;
				} else {
					append(this.content, scrollableContent.getDomNode());
					this.contentDisposables.push(scrollableContent);
				}
360
			}));
J
Joao Moreno 已提交
361 362
	}

363 364 365 366 367
	private openDependencies(extension: IExtension) {
		if (extension.dependencies.length === 0) {
			append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
			return;
		}
S
Sandeep Somavarapu 已提交
368 369 370 371 372 373 374 375 376
		addClass(this.content, 'loading');
		this.extensionDependencies.get().then(extensionDependencies => {
			removeClass(this.content, 'loading');

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

377
			const tree = ExtensionEditor.renderDependencies(content, extensionDependencies, this.instantiationService);
378 379 380 381
			const layout = () => {
				scrollableContent.scanDomNode();
				tree.layout(scrollableContent.getHeight());
			};
S
Sandeep Somavarapu 已提交
382 383 384
			const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
			this.contentDisposables.push(toDisposable(removeLayoutParticipant));

385
			this.contentDisposables.push(tree);
S
Sandeep Somavarapu 已提交
386
			scrollableContent.scanDomNode();
S
Sandeep Somavarapu 已提交
387 388 389 390 391
		}, error => {
			removeClass(this.content, 'loading');
			append(this.content, $('p.nocontent')).textContent = error;
			this.messageService.show(Severity.Error, error);
			return;
S
Sandeep Somavarapu 已提交
392 393 394
		});
	}

395
	private static renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies, instantiationService: IInstantiationService): Tree {
396
		const renderer = instantiationService.createInstance(Renderer);
397
		const controller = instantiationService.createInstance(Controller);
S
Sandeep Somavarapu 已提交
398 399
		const tree = new Tree(container, {
			dataSource: new DataSource(),
400 401
			renderer,
			controller
S
Sandeep Somavarapu 已提交
402 403 404 405 406 407 408 409
		}, {
				indentPixels: 40,
				twistiePixels: 20
			});
		tree.setInput(extensionDependencies);
		return tree;
	}

410
	private static renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
411 412
		const contributes = manifest.contributes;
		const configuration = contributes && contributes.configuration;
J
Joao Moreno 已提交
413
		const properties = configuration && configuration.properties;
J
Joao Moreno 已提交
414
		const contrib = properties ? Object.keys(properties) : [];
J
Joao Moreno 已提交
415

J
Joao Moreno 已提交
416
		if (!contrib.length) {
417
			return false;
J
Joao Moreno 已提交
418 419
		}

J
Joao Moreno 已提交
420
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
421
			$('summary', null, localize('settings', "Settings ({0})", contrib.length)),
J
Joao Moreno 已提交
422
			$('table', null,
J
Joao Moreno 已提交
423 424 425
				$('tr', null,
					$('th', null, localize('setting name', "Name")),
					$('th', null, localize('description', "Description")),
J
nls  
Joao Moreno 已提交
426
					$('th', null, localize('default', "Default"))
J
Joao Moreno 已提交
427 428 429 430 431 432
				),
				...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 已提交
433
			)
J
Joao Moreno 已提交
434 435 436
		);

		append(container, details);
437
		return true;
J
Joao Moreno 已提交
438 439
	}

440
	private static renderDebuggers(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
441 442
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.debuggers || [];
J
Joao Moreno 已提交
443

J
Joao Moreno 已提交
444
		if (!contrib.length) {
445
			return false;
J
Joao Moreno 已提交
446 447
		}

J
Joao Moreno 已提交
448
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
449
			$('summary', null, localize('debuggers', "Debuggers ({0})", contrib.length)),
J
Joao Moreno 已提交
450
			$('table', null,
J
Joao Moreno 已提交
451 452
				$('tr', null, $('th', null, localize('debugger name', "Name"))),
				...contrib.map(d => $('tr', null, $('td', null, d.label || d.type)))
J
Joao Moreno 已提交
453
			)
J
Joao Moreno 已提交
454 455 456
		);

		append(container, details);
457
		return true;
J
Joao Moreno 已提交
458 459
	}

460
	private static renderThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
461 462
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.themes || [];
J
Joao Moreno 已提交
463

J
Joao Moreno 已提交
464
		if (!contrib.length) {
465
			return false;
J
Joao Moreno 已提交
466 467
		}

J
Joao Moreno 已提交
468
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
469
			$('summary', null, localize('themes', "Themes ({0})", contrib.length)),
J
Joao Moreno 已提交
470 471 472 473
			$('ul', null, ...contrib.map(theme => $('li', null, theme.label)))
		);

		append(container, details);
474
		return true;
J
Joao Moreno 已提交
475 476
	}

477
	private static renderJSONValidation(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
478 479
		const contributes = manifest.contributes;
		const contrib = contributes && contributes.jsonValidation || [];
J
Joao Moreno 已提交
480 481

		if (!contrib.length) {
482
			return false;
J
Joao Moreno 已提交
483 484
		}

J
Joao Moreno 已提交
485
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
486
			$('summary', null, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
J
Joao Moreno 已提交
487 488 489 490
			$('ul', null, ...contrib.map(v => $('li', null, v.fileMatch)))
		);

		append(container, details);
491
		return true;
J
Joao Moreno 已提交
492 493
	}

494
	private renderCommands(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
495 496
		const contributes = manifest.contributes;
		const rawCommands = contributes && contributes.commands || [];
J
Joao Moreno 已提交
497
		const commands = rawCommands.map(c => ({
498 499
			id: c.command,
			title: c.title,
J
Joao Moreno 已提交
500
			keybindings: [],
501 502 503
			menus: []
		}));

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

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

508 509
		Object.keys(menus).forEach(context => {
			menus[context].forEach(menu => {
J
Joao Moreno 已提交
510
				let command = byId[menu.command];
511 512

				if (!command) {
J
Joao Moreno 已提交
513
					command = { id: menu.command, title: '', keybindings: [], menus: [context] };
J
Joao Moreno 已提交
514
					byId[command.id] = command;
515 516 517 518 519 520 521
					commands.push(command);
				} else {
					command.menus.push(context);
				}
			});
		});

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

524 525
		rawKeybindings.forEach(rawKeybinding => {
			const keyLabel = this.keybindingToLabel(rawKeybinding);
J
Joao Moreno 已提交
526 527 528 529 530

			if (!keyLabel) {
				return;
			}

J
Joao Moreno 已提交
531
			let command = byId[rawKeybinding.command];
J
Joao Moreno 已提交
532 533

			if (!command) {
534
				command = { id: rawKeybinding.command, title: '', keybindings: [keyLabel], menus: [] };
J
Joao Moreno 已提交
535
				byId[command.id] = command;
J
Joao Moreno 已提交
536 537
				commands.push(command);
			} else {
538
				command.keybindings.push(keyLabel);
J
Joao Moreno 已提交
539 540 541
			}
		});

542
		if (!commands.length) {
543
			return false;
544 545
		}

J
Joao Moreno 已提交
546
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
547 548 549 550 551
			$('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 已提交
552
					$('th', null, localize('keyboard shortcuts', "Keyboard Shortcuts")),
553 554 555
					$('th', null, localize('menuContexts', "Menu Contexts"))
				),
				...commands.map(c => $('tr', null,
556
					$('td', null, $('code', null, c.id)),
557
					$('td', null, c.title),
558
					$('td', null, ...join(c.keybindings.map(keybinding => $('code', null, keybinding)), ' ')),
559 560 561
					$('td', null, ...c.menus.map(context => $('code', null, context)))
				))
			)
J
Joao Moreno 已提交
562 563 564
		);

		append(container, details);
565
		return true;
566 567
	}

568
	private static renderLanguages(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
J
Joao Moreno 已提交
569 570
		const contributes = manifest.contributes;
		const rawLanguages = contributes && contributes.languages || [];
J
Joao Moreno 已提交
571 572
		const languages = rawLanguages.map(l => ({
			id: l.id,
J
Joao Moreno 已提交
573
			name: (l.aliases || [])[0] || l.id,
J
Joao Moreno 已提交
574 575 576
			extensions: l.extensions || [],
			hasGrammar: false,
			hasSnippets: false
J
Joao Moreno 已提交
577 578
		}));

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

J
Joao Moreno 已提交
581
		const grammars = contributes && contributes.grammars || [];
J
Joao Moreno 已提交
582 583 584 585 586

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

			if (!language) {
J
Joao Moreno 已提交
587
				language = { id: grammar.language, name: grammar.language, extensions: [], hasGrammar: true, hasSnippets: false };
J
Joao Moreno 已提交
588 589 590 591 592 593 594
				byId[language.id] = language;
				languages.push(language);
			} else {
				language.hasGrammar = true;
			}
		});

J
Joao Moreno 已提交
595 596 597 598 599 600 601 602 603 604 605 606 607 608
		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 已提交
609
		if (!languages.length) {
610
			return false;
J
Joao Moreno 已提交
611 612
		}

J
Joao Moreno 已提交
613
		const details = $('details', { open: true, ontoggle: onDetailsToggle },
J
Joao Moreno 已提交
614 615 616
			$('summary', null, localize('languages', "Languages ({0})", languages.length)),
			$('table', null,
				$('tr', null,
J
Joao Moreno 已提交
617 618
					$('th', null, localize('language id', "ID")),
					$('th', null, localize('language name', "Name")),
J
Joao Moreno 已提交
619
					$('th', null, localize('file extensions', "File Extensions")),
J
Joao Moreno 已提交
620 621
					$('th', null, localize('grammar', "Grammar")),
					$('th', null, localize('snippets', "Snippets"))
J
Joao Moreno 已提交
622 623
				),
				...languages.map(l => $('tr', null,
J
Joao Moreno 已提交
624
					$('td', null, l.id),
J
Joao Moreno 已提交
625
					$('td', null, l.name),
J
Joao Moreno 已提交
626
					$('td', null, ...join(l.extensions.map(ext => $('code', null, ext)), ' ')),
J
Joao Moreno 已提交
627 628
					$('td', null, document.createTextNode(l.hasGrammar ? '✔︎' : '')),
					$('td', null, document.createTextNode(l.hasSnippets ? '✔︎' : ''))
J
Joao Moreno 已提交
629 630
				))
			)
J
Joao Moreno 已提交
631 632 633
		);

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

637 638 639
	private keybindingToLabel(rawKeyBinding: IKeyBinding): string {
		let key: string;

J
Johannes Rieken 已提交
640
		switch (process.platform) {
641 642 643 644 645 646
			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));
J
Joao Moreno 已提交
647 648
		const result = this.keybindingService.getLabelFor(keyBinding);
		return result === 'unknown' ? null : result;
649 650
	}

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

J
Joao Moreno 已提交
654 655
		let promise = loadingTask();
		promise = always(promise, () => removeClass(this.content, 'loading'));
J
Joao Moreno 已提交
656

J
Joao Moreno 已提交
657
		this.contentDisposables.push(toDisposable(() => promise.cancel()));
J
Joao Moreno 已提交
658 659 660
	}

	layout(): void {
J
Joao Moreno 已提交
661
		this.layoutParticipants.forEach(p => p.layout());
J
Joao Moreno 已提交
662 663
	}

664 665 666 667 668 669 670 671
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

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

J
Joao Moreno 已提交
672 673
	dispose(): void {
		this._highlight = null;
J
Joao Moreno 已提交
674 675
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
676 677 678
		super.dispose();
	}
}