extensionsViews.ts 43.8 KB
Newer Older
S
Sandeep Somavarapu 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { localize } from 'vs/nls';
M
Matt Bierner 已提交
7
import { Disposable } from 'vs/base/common/lifecycle';
S
Sandeep Somavarapu 已提交
8
import { assign } from 'vs/base/common/objects';
J
Joao Moreno 已提交
9
import { Event, Emitter } from 'vs/base/common/event';
S
Sandeep Somavarapu 已提交
10
import { isPromiseCanceledError, getErrorMessage } from 'vs/base/common/errors';
J
Joao Moreno 已提交
11
import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging';
12 13
import { SortBy, SortOrder, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
S
Sandeep Somavarapu 已提交
14 15 16
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
17
import { append, $, toggleClass } from 'vs/base/browser/dom';
S
Sandeep Somavarapu 已提交
18
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
19
import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/browser/extensionsList';
20
import { IExtension, IExtensionsWorkbenchService, ExtensionState } from '../common/extensions';
S
Sandeep Somavarapu 已提交
21
import { Query } from '../common/extensionQuery';
22
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
S
Sandeep Somavarapu 已提交
23
import { IThemeService } from 'vs/platform/theme/common/themeService';
24
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
25
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
26
import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions';
27
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
S
Sandeep Somavarapu 已提交
28 29
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
S
Sandeep Somavarapu 已提交
30
import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
31
import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
32
import { WorkbenchPagedList } from 'vs/platform/list/browser/listService';
33
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
34
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
35
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
S
Sandeep Somavarapu 已提交
36
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
S
Sandeep Somavarapu 已提交
37
import { distinct, coalesce } from 'vs/base/common/arrays';
38
import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService';
39
import { alert } from 'vs/base/browser/ui/aria/aria';
S
Sandeep Somavarapu 已提交
40
import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
A
Alex Dima 已提交
41
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
42
import { CancellationToken } from 'vs/base/common/cancellation';
S
Sandeep Somavarapu 已提交
43
import { IAction } from 'vs/base/common/actions';
44
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
45
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
S
Sandeep Somavarapu 已提交
46
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
A
Alex Dima 已提交
47 48
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IProductService } from 'vs/platform/product/common/product';
49
import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon';
S
Sandeep Somavarapu 已提交
50
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
S
Sandeep Somavarapu 已提交
51

S
Sandeep Somavarapu 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
class ExtensionsViewState extends Disposable implements IExtensionsViewState {

	private readonly _onFocus: Emitter<IExtension> = this._register(new Emitter<IExtension>());
	readonly onFocus: Event<IExtension> = this._onFocus.event;

	private readonly _onBlur: Emitter<IExtension> = this._register(new Emitter<IExtension>());
	readonly onBlur: Event<IExtension> = this._onBlur.event;

	private currentlyFocusedItems: IExtension[] = [];

	onFocusChange(extensions: IExtension[]): void {
		this.currentlyFocusedItems.forEach(extension => this._onBlur.fire(extension));
		this.currentlyFocusedItems = extensions;
		this.currentlyFocusedItems.forEach(extension => this._onFocus.fire(extension));
	}
}

69 70 71 72
export interface ExtensionsListViewOptions extends IViewletViewOptions {
	server?: IExtensionManagementServer;
}

S
Sandeep Somavarapu 已提交
73 74
class ExtensionListViewWarning extends Error { }

75
export class ExtensionsListView extends ViewletPanel {
S
Sandeep Somavarapu 已提交
76

77
	private readonly server: IExtensionManagementServer | undefined;
S
Sandeep Somavarapu 已提交
78
	private messageContainer: HTMLElement;
79
	private messageSeverityIcon: HTMLElement;
S
Sandeep Somavarapu 已提交
80 81 82
	private messageBox: HTMLElement;
	private extensionsList: HTMLElement;
	private badge: CountBadge;
83
	protected badgeContainer: HTMLElement;
84
	private list: WorkbenchPagedList<IExtension> | null;
S
Sandeep Somavarapu 已提交
85
	private queryRequest: { query: string, request: CancelablePromise<IPagedModel<IExtension>> } | null;
S
Sandeep Somavarapu 已提交
86 87

	constructor(
88
		options: ExtensionsListViewOptions,
89
		@INotificationService protected notificationService: INotificationService,
S
Sandeep Somavarapu 已提交
90 91
		@IKeybindingService keybindingService: IKeybindingService,
		@IContextMenuService contextMenuService: IContextMenuService,
92
		@IInstantiationService protected instantiationService: IInstantiationService,
93 94
		@IThemeService private readonly themeService: IThemeService,
		@IExtensionService private readonly extensionService: IExtensionService,
S
Sandeep Somavarapu 已提交
95
		@IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService,
96
		@IEditorService private readonly editorService: IEditorService,
97
		@IExtensionTipsService protected tipsService: IExtensionTipsService,
98
		@ITelemetryService private readonly telemetryService: ITelemetryService,
S
Sandeep Somavarapu 已提交
99 100
		@IConfigurationService configurationService: IConfigurationService,
		@IWorkspaceContextService protected contextService: IWorkspaceContextService,
101
		@IExperimentService private readonly experimentService: IExperimentService,
102
		@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
A
Alex Dima 已提交
103 104
		@IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService,
		@IProductService protected readonly productService: IProductService,
S
Sandeep Somavarapu 已提交
105
		@IContextKeyService contextKeyService: IContextKeyService,
S
Sandeep Somavarapu 已提交
106
	) {
S
Sandeep Somavarapu 已提交
107
		super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
108
		this.server = options.server;
S
Sandeep Somavarapu 已提交
109 110
	}

111 112 113 114
	protected renderHeader(container: HTMLElement): void {
		this.renderHeaderTitle(container);
	}

J
Jackson Kearl 已提交
115
	renderHeaderTitle(container: HTMLElement): void {
116
		super.renderHeaderTitle(container, this.title);
117

J
Jackson Kearl 已提交
118
		this.badgeContainer = append(container, $('.count-badge-wrapper'));
119
		this.badge = new CountBadge(this.badgeContainer);
M
Matt Bierner 已提交
120
		this._register(attachBadgeStyler(this.badge, this.themeService));
S
Sandeep Somavarapu 已提交
121 122 123 124
	}

	renderBody(container: HTMLElement): void {
		this.extensionsList = append(container, $('.extensions-list'));
S
Sandeep Somavarapu 已提交
125
		this.messageContainer = append(container, $('.message-container'));
126
		this.messageSeverityIcon = append(this.messageContainer, $(''));
S
Sandeep Somavarapu 已提交
127
		this.messageBox = append(this.messageContainer, $('.message'));
S
Sandeep Somavarapu 已提交
128
		const delegate = new Delegate();
S
Sandeep Somavarapu 已提交
129 130
		const extensionsViewState = new ExtensionsViewState();
		const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState);
131
		this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], {
J
Joao Moreno 已提交
132
			ariaLabel: localize('extensions', "Extensions"),
J
Joao Moreno 已提交
133
			multipleSelectionSupport: false,
134 135
			setRowLineHeight: false,
			horizontalScrolling: false
136
		}) as WorkbenchPagedList<IExtension>;
M
Matt Bierner 已提交
137 138 139 140
		this._register(this.list.onContextMenu(e => this.onContextMenu(e), this));
		this._register(this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this));
		this._register(this.list);
		this._register(extensionsViewState);
S
Sandeep Somavarapu 已提交
141

M
Matt Bierner 已提交
142
		this._register(Event.chain(this.list.onOpen)
S
Sandeep Somavarapu 已提交
143 144
			.map(e => e.elements[0])
			.filter(e => !!e)
M
Matt Bierner 已提交
145
			.on(this.openExtension, this));
S
Sandeep Somavarapu 已提交
146

M
Matt Bierner 已提交
147
		this._register(Event.chain(this.list.onPin)
S
Sandeep Somavarapu 已提交
148 149
			.map(e => e.elements[0])
			.filter(e => !!e)
M
Matt Bierner 已提交
150
			.on(this.pin, this));
S
Sandeep Somavarapu 已提交
151 152
	}

153 154
	protected layoutBody(height: number, width: number): void {
		this.extensionsList.style.height = height + 'px';
155 156 157
		if (this.list) {
			this.list.layout(height, width);
		}
S
Sandeep Somavarapu 已提交
158 159
	}

160
	async show(query: string): Promise<IPagedModel<IExtension>> {
S
Sandeep Somavarapu 已提交
161 162 163 164 165 166 167 168
		if (this.queryRequest) {
			if (this.queryRequest.query === query) {
				return this.queryRequest.request;
			}
			this.queryRequest.request.cancel();
			this.queryRequest = null;
		}

169 170 171 172 173 174 175 176 177 178 179 180
		const parsedQuery = Query.parse(query);

		let options: IQueryOptions = {
			sortOrder: SortOrder.Default
		};

		switch (parsedQuery.sortBy) {
			case 'installs': options = assign(options, { sortBy: SortBy.InstallCount }); break;
			case 'rating': options = assign(options, { sortBy: SortBy.WeightedRating }); break;
			case 'name': options = assign(options, { sortBy: SortBy.Title }); break;
		}

M
Matt Bierner 已提交
181
		const successCallback = (model: IPagedModel<IExtension>) => {
S
Sandeep Somavarapu 已提交
182
			this.queryRequest = null;
183 184
			this.setModel(model);
			return model;
185
		};
M
Matt Bierner 已提交
186 187


S
Sandeep Somavarapu 已提交
188
		const errorCallback = (e: any) => {
S
Sandeep Somavarapu 已提交
189
			const model = new PagedModel([]);
S
Sandeep Somavarapu 已提交
190 191
			if (!isPromiseCanceledError(e)) {
				this.queryRequest = null;
S
Sandeep Somavarapu 已提交
192
				this.setModel(model, e);
S
Sandeep Somavarapu 已提交
193
			}
S
Sandeep Somavarapu 已提交
194
			return this.list ? this.list.model : model;
195 196
		};

S
Sandeep Somavarapu 已提交
197
		const request = createCancelablePromise(token => this.query(parsedQuery, options, token).then(successCallback).catch(errorCallback));
S
Sandeep Somavarapu 已提交
198
		this.queryRequest = { query, request };
S
Sandeep Somavarapu 已提交
199
		return request;
S
Sandeep Somavarapu 已提交
200 201 202
	}

	count(): number {
203
		return this.list ? this.list.length : 0;
S
Sandeep Somavarapu 已提交
204 205
	}

206
	protected showEmptyModel(): Promise<IPagedModel<IExtension>> {
207 208
		const emptyModel = new PagedModel([]);
		this.setModel(emptyModel);
209
		return Promise.resolve(emptyModel);
210 211
	}

212
	private async onContextMenu(e: IListContextMenuEvent<IExtension>): Promise<void> {
S
Sandeep Somavarapu 已提交
213
		if (e.element) {
214 215 216 217 218 219 220 221 222 223 224 225 226 227
			const runningExtensions = await this.extensionService.getExtensions();
			const colorThemes = await this.workbenchThemeService.getColorThemes();
			const fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
			const manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction);
			manageExtensionAction.extension = e.element;
			const groups = manageExtensionAction.getActionGroups(runningExtensions, colorThemes, fileIconThemes);
			let actions: IAction[] = [];
			for (const menuActions of groups) {
				actions = [...actions, ...menuActions, new Separator()];
			}
			if (manageExtensionAction.enabled) {
				this.contextMenuService.showContextMenu({
					getAnchor: () => e.anchor,
					getActions: () => actions.slice(0, actions.length - 1)
S
Sandeep Somavarapu 已提交
228
				});
229
			}
S
Sandeep Somavarapu 已提交
230 231 232
		}
	}

S
Sandeep Somavarapu 已提交
233 234 235 236 237 238 239 240 241 242 243
	private async query(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
		const idRegex = /@id:(([a-z0-9A-Z][a-z0-9\-A-Z]*)\.([a-z0-9A-Z][a-z0-9\-A-Z]*))/g;
		const ids: string[] = [];
		let idMatch;
		while ((idMatch = idRegex.exec(query.value)) !== null) {
			const name = idMatch[1];
			ids.push(name);
		}
		if (ids.length) {
			return this.queryByIds(ids, options, token);
		}
244
		if (ExtensionsListView.isLocalExtensionsQuery(query.value) || /@builtin/.test(query.value)) {
S
Sandeep Somavarapu 已提交
245 246
			return this.queryLocal(query, options);
		}
S
Sandeep Somavarapu 已提交
247 248 249 250 251
		return this.queryGallery(query, options, token)
			.then(null, e => {
				console.warn('Error querying extensions gallery', getErrorMessage(e));
				return Promise.reject(new ExtensionListViewWarning(localize('galleryError', "We cannot connect to the Extensions Marketplace at this time, please try again later.")));
			});
S
Sandeep Somavarapu 已提交
252 253 254 255
	}

	private async queryByIds(ids: string[], options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
		const idsSet: Set<string> = ids.reduce((result, id) => { result.add(id.toLowerCase()); return result; }, new Set<string>());
256
		const result = (await this.extensionsWorkbenchService.queryLocal(this.server))
S
Sandeep Somavarapu 已提交
257 258 259 260 261 262 263 264 265 266
			.filter(e => idsSet.has(e.identifier.id.toLowerCase()));

		if (result.length) {
			return this.getPagedModel(this.sortExtensions(result, options));
		}

		return this.extensionsWorkbenchService.queryGallery({ names: ids, source: 'queryById' }, token)
			.then(pager => this.getPagedModel(pager));
	}

267 268
	private async queryLocal(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
		let value = query.value;
269
		if (/@builtin/i.test(value)) {
270 271 272 273 274 275 276 277
			const showThemesOnly = /@builtin:themes/i.test(value);
			if (showThemesOnly) {
				value = value.replace(/@builtin:themes/g, '');
			}
			const showBasicsOnly = /@builtin:basics/i.test(value);
			if (showBasicsOnly) {
				value = value.replace(/@builtin:basics/g, '');
			}
278 279 280 281
			const showFeaturesOnly = /@builtin:features/i.test(value);
			if (showFeaturesOnly) {
				value = value.replace(/@builtin:features/g, '');
			}
282

283
			value = value.replace(/@builtin/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
284
			let result = await this.extensionsWorkbenchService.queryLocal(this.server);
S
Sandeep Somavarapu 已提交
285 286

			result = result
287
				.filter(e => e.type === ExtensionType.System && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1));
S
Sandeep Somavarapu 已提交
288

289 290
			if (showThemesOnly) {
				const themesExtensions = result.filter(e => {
291 292
					return e.local
						&& e.local.manifest
293 294 295 296
						&& e.local.manifest.contributes
						&& Array.isArray(e.local.manifest.contributes.themes)
						&& e.local.manifest.contributes.themes.length;
				});
297
				return this.getPagedModel(this.sortExtensions(themesExtensions, options));
298 299 300
			}
			if (showBasicsOnly) {
				const basics = result.filter(e => {
301
					return e.local && e.local.manifest
302
						&& e.local.manifest.contributes
303 304
						&& Array.isArray(e.local.manifest.contributes.grammars)
						&& e.local.manifest.contributes.grammars.length
305
						&& e.local.identifier.id !== 'vscode.git';
306
				});
307
				return this.getPagedModel(this.sortExtensions(basics, options));
308
			}
309 310
			if (showFeaturesOnly) {
				const others = result.filter(e => {
311 312
					return e.local
						&& e.local.manifest
313
						&& e.local.manifest.contributes
314
						&& (!Array.isArray(e.local.manifest.contributes.grammars) || e.local.identifier.id === 'vscode.git')
315 316
						&& !Array.isArray(e.local.manifest.contributes.themes);
				});
317
				return this.getPagedModel(this.sortExtensions(others, options));
318
			}
319

320
			return this.getPagedModel(this.sortExtensions(result, options));
S
Sandeep Somavarapu 已提交
321 322
		}

323 324 325 326 327 328 329 330 331
		const categories: string[] = [];
		value = value.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => {
			const entry = (category || quotedCategory || '').toLowerCase();
			if (categories.indexOf(entry) === -1) {
				categories.push(entry);
			}
			return '';
		});

332
		if (/@installed/i.test(value)) {
S
Sandeep Somavarapu 已提交
333
			// Show installed extensions
334
			value = value.replace(/@installed/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
S
Sandeep Somavarapu 已提交
335

336
			let result = await this.extensionsWorkbenchService.queryLocal(this.server);
S
Sandeep Somavarapu 已提交
337 338

			result = result
339
				.filter(e => e.type === ExtensionType.User
340
					&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
341
					&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
S
Sandeep Somavarapu 已提交
342

343 344 345 346 347 348 349 350 351 352
			if (options.sortBy !== undefined) {
				result = this.sortExtensions(result, options);
			} else {
				const runningExtensions = await this.extensionService.getExtensions();
				const runningExtensionsById = runningExtensions.reduce((result, e) => { result.set(ExtensionIdentifier.toKey(e.identifier.value), e); return result; }, new Map<string, IExtensionDescription>());
				result = result.sort((e1, e2) => {
					const running1 = runningExtensionsById.get(ExtensionIdentifier.toKey(e1.identifier.id));
					const isE1Running = running1 && this.extensionManagementServerService.getExtensionManagementServer(running1.extensionLocation) === e1.server;
					const running2 = runningExtensionsById.get(ExtensionIdentifier.toKey(e2.identifier.id));
					const isE2Running = running2 && this.extensionManagementServerService.getExtensionManagementServer(running2.extensionLocation) === e2.server;
353
					if ((isE1Running && isE2Running)) {
354 355
						return e1.displayName.localeCompare(e2.displayName);
					}
356 357
					const isE1LanguagePackExtension = e1.local && isLanguagePackExtension(e1.local.manifest);
					const isE2LanguagePackExtension = e2.local && isLanguagePackExtension(e2.local.manifest);
358 359 360 361 362 363 364 365 366
					if (!isE1Running && !isE2Running) {
						if (isE1LanguagePackExtension) {
							return -1;
						}
						if (isE2LanguagePackExtension) {
							return 1;
						}
						return e1.displayName.localeCompare(e2.displayName);
					}
367 368 369
					if ((isE1Running && isE2LanguagePackExtension) || (isE2Running && isE1LanguagePackExtension)) {
						return e1.displayName.localeCompare(e2.displayName);
					}
370 371 372 373
					return isE1Running ? -1 : 1;
				});
			}
			return this.getPagedModel(result);
S
Sandeep Somavarapu 已提交
374 375
		}

J
Joao Moreno 已提交
376

S
Sandeep Somavarapu 已提交
377
		if (/@outdated/i.test(value)) {
S
Sandeep Somavarapu 已提交
378
			value = value.replace(/@outdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
S
Sandeep Somavarapu 已提交
379

380
			const local = await this.extensionsWorkbenchService.queryLocal(this.server);
S
Sandeep Somavarapu 已提交
381 382
			const result = local
				.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
383 384
				.filter(extension => extension.outdated
					&& (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)
385
					&& (!categories.length || categories.some(category => !!extension.local && extension.local.manifest.categories!.some(c => c.toLowerCase() === category))));
S
Sandeep Somavarapu 已提交
386

387
			return this.getPagedModel(this.sortExtensions(result, options));
S
Sandeep Somavarapu 已提交
388 389
		}

390
		if (/@disabled/i.test(value)) {
S
Sandeep Somavarapu 已提交
391
			value = value.replace(/@disabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
S
Sandeep Somavarapu 已提交
392

393
			const local = await this.extensionsWorkbenchService.queryLocal(this.server);
S
Sandeep Somavarapu 已提交
394 395 396 397
			const runningExtensions = await this.extensionService.getExtensions();

			const result = local
				.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
S
Sandeep Somavarapu 已提交
398
				.filter(e => runningExtensions.every(r => !areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier))
399
					&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
400
					&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
S
Sandeep Somavarapu 已提交
401

402
			return this.getPagedModel(this.sortExtensions(result, options));
S
Sandeep Somavarapu 已提交
403 404
		}

405
		if (/@enabled/i.test(value)) {
S
Sandeep Somavarapu 已提交
406
			value = value ? value.replace(/@enabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase() : '';
407

408
			const local = (await this.extensionsWorkbenchService.queryLocal(this.server)).filter(e => e.type === ExtensionType.User);
409
			const runningExtensions = await this.extensionService.getExtensions();
410

411
			const result = local
412
				.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
S
Sandeep Somavarapu 已提交
413
				.filter(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier))
414
					&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
415
					&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
416

417
			return this.getPagedModel(this.sortExtensions(result, options));
418 419
		}

420 421 422
		return new PagedModel([]);
	}

S
Sandeep Somavarapu 已提交
423
	private async queryGallery(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
424
		const hasUserDefinedSortOrder = options.sortBy !== undefined;
425
		if (!hasUserDefinedSortOrder && !query.value.trim()) {
426 427 428
			options.sortBy = SortBy.InstallCount;
		}

S
Sandeep Somavarapu 已提交
429
		if (ExtensionsListView.isWorkspaceRecommendedExtensionsQuery(query.value)) {
S
Sandeep Somavarapu 已提交
430
			return this.getWorkspaceRecommendationsModel(query, options, token);
S
Sandeep Somavarapu 已提交
431
		} else if (ExtensionsListView.isKeymapsRecommendedExtensionsQuery(query.value)) {
S
Sandeep Somavarapu 已提交
432
			return this.getKeymapRecommendationsModel(query, options, token);
433
		} else if (/@recommended:all/i.test(query.value) || ExtensionsListView.isSearchRecommendedExtensionsQuery(query.value)) {
S
Sandeep Somavarapu 已提交
434
			return this.getAllRecommendationsModel(query, options, token);
S
Sandeep Somavarapu 已提交
435
		} else if (ExtensionsListView.isRecommendedExtensionsQuery(query.value)) {
S
Sandeep Somavarapu 已提交
436
			return this.getRecommendationsModel(query, options, token);
S
Sandeep Somavarapu 已提交
437 438
		}

R
Ramya Rao 已提交
439
		if (/\bcurated:([^\s]+)\b/.test(query.value)) {
S
Sandeep Somavarapu 已提交
440
			return this.getCuratedModel(query, options, token);
R
Ramya Rao 已提交
441 442
		}

S
Sandeep Somavarapu 已提交
443
		const text = query.value;
S
Sandeep Somavarapu 已提交
444

S
Sandeep Somavarapu 已提交
445 446 447
		if (/\bext:([^\s]+)\b/g.test(text)) {
			options = assign(options, { text, source: 'file-extension-tags' });
			return this.extensionsWorkbenchService.queryGallery(options, token).then(pager => this.getPagedModel(pager));
S
Sandeep Somavarapu 已提交
448 449
		}

R
Ramya Achutha Rao 已提交
450
		let preferredResults: string[] = [];
S
Sandeep Somavarapu 已提交
451
		if (text) {
452
			options = assign(options, { text: text.substr(0, 350), source: 'searchText' });
453
			if (!hasUserDefinedSortOrder) {
S
Sandeep Somavarapu 已提交
454
				const searchExperiments = await this.getSearchExperiments();
455
				for (const experiment of searchExperiments) {
456
					if (experiment.action && text.toLowerCase() === experiment.action.properties['searchText'] && Array.isArray(experiment.action.properties['preferredResults'])) {
457 458
						preferredResults = experiment.action.properties['preferredResults'];
						options.source += `-experiment-${experiment.id}`;
459 460
						break;
					}
R
Ramya Achutha Rao 已提交
461 462
				}
			}
463 464
		} else {
			options.source = 'viewlet';
S
Sandeep Somavarapu 已提交
465 466
		}

S
Sandeep Somavarapu 已提交
467
		const pager = await this.extensionsWorkbenchService.queryGallery(options, token);
R
Ramya Achutha Rao 已提交
468 469

		let positionToUpdate = 0;
470
		for (const preferredResult of preferredResults) {
R
Ramya Achutha Rao 已提交
471
			for (let j = positionToUpdate; j < pager.firstPage.length; j++) {
472
				if (areSameExtensions(pager.firstPage[j].identifier, { id: preferredResult })) {
R
Ramya Achutha Rao 已提交
473 474 475 476
					if (positionToUpdate !== j) {
						const preferredExtension = pager.firstPage.splice(j, 1)[0];
						pager.firstPage.splice(positionToUpdate, 0, preferredExtension);
						positionToUpdate++;
R
Ramya Achutha Rao 已提交
477
					}
R
Ramya Achutha Rao 已提交
478
					break;
R
Ramya Achutha Rao 已提交
479 480
				}
			}
R
Ramya Achutha Rao 已提交
481
		}
482
		return this.getPagedModel(pager);
R
Ramya Achutha Rao 已提交
483

S
Sandeep Somavarapu 已提交
484 485
	}

J
Johannes Rieken 已提交
486 487
	private _searchExperiments: Promise<IExperiment[]>;
	private getSearchExperiments(): Promise<IExperiment[]> {
S
Sandeep Somavarapu 已提交
488 489 490 491 492 493
		if (!this._searchExperiments) {
			this._searchExperiments = this.experimentService.getExperimentsByType(ExperimentActionType.ExtensionSearchResults);
		}
		return this._searchExperiments;
	}

S
Sandeep Somavarapu 已提交
494 495 496
	private sortExtensions(extensions: IExtension[], options: IQueryOptions): IExtension[] {
		switch (options.sortBy) {
			case SortBy.InstallCount:
497
				extensions = extensions.sort((e1, e2) => typeof e2.installCount === 'number' && typeof e1.installCount === 'number' ? e2.installCount - e1.installCount : NaN);
S
Sandeep Somavarapu 已提交
498 499 500
				break;
			case SortBy.AverageRating:
			case SortBy.WeightedRating:
501
				extensions = extensions.sort((e1, e2) => typeof e2.rating === 'number' && typeof e1.rating === 'number' ? e2.rating - e1.rating : NaN);
S
Sandeep Somavarapu 已提交
502 503 504 505 506 507 508 509 510 511 512
				break;
			default:
				extensions = extensions.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName));
				break;
		}
		if (options.sortOrder === SortOrder.Descending) {
			extensions = extensions.reverse();
		}
		return extensions;
	}

513
	// Get All types of recommendations, trimmed to show a max of 8 at any given time
S
Sandeep Somavarapu 已提交
514
	private getAllRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
515
		const value = query.value.replace(/@recommended:all/g, '').replace(/@recommended/g, '').trim().toLowerCase();
S
Sandeep Somavarapu 已提交
516

517
		return this.extensionsWorkbenchService.queryLocal(this.server)
518
			.then(result => result.filter(e => e.type === ExtensionType.User))
519
			.then(local => {
S
Sandeep Somavarapu 已提交
520
				const fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
521 522
				const othersPromise = this.tipsService.getOtherRecommendations();
				const workspacePromise = this.tipsService.getWorkspaceRecommendations();
523

524
				return Promise.all([othersPromise, workspacePromise])
525
					.then(([others, workspaceRecommendations]) => {
S
Sandeep Somavarapu 已提交
526
						const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, workspaceRecommendations);
527
						const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
528 529
						/* __GDPR__
							"extensionAllRecommendations:open" : {
530 531
								"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
								"recommendations": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
532 533
							}
						*/
534 535 536 537 538
						this.telemetryService.publicLog('extensionAllRecommendations:open', {
							count: names.length,
							recommendations: names.map(id => {
								return {
									id,
R
Ramya Achutha Rao 已提交
539
									recommendationReason: recommendationsWithReason[id.toLowerCase()].reasonId
540 541 542
								};
							})
						});
543
						if (!names.length) {
544
							return Promise.resolve(new PagedModel([]));
545
						}
546
						options.source = 'recommendations-all';
S
Sandeep Somavarapu 已提交
547
						return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token)
548 549
							.then(pager => {
								this.sortFirstPage(pager, names);
550
								return this.getPagedModel(pager || []);
551
							});
552
					});
S
Sandeep Somavarapu 已提交
553 554 555
			});
	}

S
Sandeep Somavarapu 已提交
556
	private async getCuratedModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
R
Ramya Rao 已提交
557
		const value = query.value.replace(/curated:/g, '').trim();
558 559 560
		const names = await this.experimentService.getCuratedExtensionsList(value);
		if (Array.isArray(names) && names.length) {
			options.source = `curated:${value}`;
S
Sandeep Somavarapu 已提交
561
			const pager = await this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token);
562
			this.sortFirstPage(pager, names);
563
			return this.getPagedModel(pager || []);
564 565
		}
		return new PagedModel([]);
R
Ramya Rao 已提交
566 567
	}

568
	// Get All types of recommendations other than Workspace recommendations, trimmed to show a max of 8 at any given time
S
Sandeep Somavarapu 已提交
569
	private getRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
S
Sandeep Somavarapu 已提交
570 571
		const value = query.value.replace(/@recommended/g, '').trim().toLowerCase();

572
		return this.extensionsWorkbenchService.queryLocal(this.server)
573
			.then(result => result.filter(e => e.type === ExtensionType.User))
S
Sandeep Somavarapu 已提交
574
			.then(local => {
R
Ramya Achutha Rao 已提交
575
				let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
576 577
				const othersPromise = this.tipsService.getOtherRecommendations();
				const workspacePromise = this.tipsService.getWorkspaceRecommendations();
R
Ramya Achutha Rao 已提交
578

579
				return Promise.all([othersPromise, workspacePromise])
580
					.then(([others, workspaceRecommendations]) => {
S
Sandeep Somavarapu 已提交
581
						fileBasedRecommendations = fileBasedRecommendations.filter(x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
M
Matt Bierner 已提交
582
						others = others.filter(x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
R
Ramya Achutha Rao 已提交
583

S
Sandeep Somavarapu 已提交
584
						const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, []);
585
						const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
586 587 588

						/* __GDPR__
							"extensionRecommendations:open" : {
589 590
								"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
								"recommendations": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
591 592
							}
						*/
593 594 595 596 597
						this.telemetryService.publicLog('extensionRecommendations:open', {
							count: names.length,
							recommendations: names.map(id => {
								return {
									id,
R
Ramya Achutha Rao 已提交
598
									recommendationReason: recommendationsWithReason[id.toLowerCase()].reasonId
599 600 601
								};
							})
						});
602 603

						if (!names.length) {
604
							return Promise.resolve(new PagedModel([]));
605 606
						}
						options.source = 'recommendations';
S
Sandeep Somavarapu 已提交
607
						return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token)
608 609
							.then(pager => {
								this.sortFirstPage(pager, names);
610
								return this.getPagedModel(pager || []);
611
							});
612
					});
S
Sandeep Somavarapu 已提交
613 614 615
			});
	}

R
Ramya Achutha Rao 已提交
616
	// Given all recommendations, trims and returns recommendations in the relevant order after filtering out installed extensions
S
Sandeep Somavarapu 已提交
617
	private getTrimmedRecommendations(installedExtensions: IExtension[], value: string, fileBasedRecommendations: IExtensionRecommendation[], otherRecommendations: IExtensionRecommendation[], workpsaceRecommendations: IExtensionRecommendation[]): string[] {
R
Ramya Achutha Rao 已提交
618 619
		const totalCount = 8;
		workpsaceRecommendations = workpsaceRecommendations
S
Sandeep Somavarapu 已提交
620 621 622
			.filter(recommendation => {
				return !this.isRecommendationInstalled(recommendation, installedExtensions)
					&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
R
Ramya Achutha Rao 已提交
623
			});
S
Sandeep Somavarapu 已提交
624 625 626 627
		fileBasedRecommendations = fileBasedRecommendations.filter(recommendation => {
			return !this.isRecommendationInstalled(recommendation, installedExtensions)
				&& workpsaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
				&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
R
Ramya Achutha Rao 已提交
628
		});
S
Sandeep Somavarapu 已提交
629 630 631 632 633
		otherRecommendations = otherRecommendations.filter(recommendation => {
			return !this.isRecommendationInstalled(recommendation, installedExtensions)
				&& fileBasedRecommendations.every(fileBasedRecommendation => fileBasedRecommendation.extensionId !== recommendation.extensionId)
				&& workpsaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
				&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
R
Ramya Achutha Rao 已提交
634 635
		});

S
Sandeep Somavarapu 已提交
636 637 638 639 640 641 642 643 644 645
		const otherCount = Math.min(2, otherRecommendations.length);
		const fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workpsaceRecommendations.length - otherCount);
		const recommendations = workpsaceRecommendations;
		recommendations.push(...fileBasedRecommendations.splice(0, fileBasedCount));
		recommendations.push(...otherRecommendations.splice(0, otherCount));

		return distinct(recommendations.map(({ extensionId }) => extensionId));
	}

	private isRecommendationInstalled(recommendation: IExtensionRecommendation, installed: IExtension[]): boolean {
S
Sandeep Somavarapu 已提交
646
		return installed.some(i => areSameExtensions(i.identifier, { id: recommendation.extensionId }));
R
Ramya Achutha Rao 已提交
647 648
	}

S
Sandeep Somavarapu 已提交
649
	private getWorkspaceRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
S
Sandeep Somavarapu 已提交
650 651 652
		const value = query.value.replace(/@recommended:workspace/g, '').trim().toLowerCase();
		return this.tipsService.getWorkspaceRecommendations()
			.then(recommendations => {
S
Sandeep Somavarapu 已提交
653
				const names = recommendations.map(({ extensionId }) => extensionId).filter(name => name.toLowerCase().indexOf(value) > -1);
K
kieferrm 已提交
654
				/* __GDPR__
655 656 657 658
					"extensionWorkspaceRecommendations:open" : {
						"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
					}
				*/
S
Sandeep Somavarapu 已提交
659 660 661
				this.telemetryService.publicLog('extensionWorkspaceRecommendations:open', { count: names.length });

				if (!names.length) {
662
					return Promise.resolve(new PagedModel([]));
S
Sandeep Somavarapu 已提交
663
				}
664
				options.source = 'recommendations-workspace';
S
Sandeep Somavarapu 已提交
665
				return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token)
666
					.then(pager => this.getPagedModel(pager || []));
S
Sandeep Somavarapu 已提交
667 668 669
			});
	}

S
Sandeep Somavarapu 已提交
670
	private getKeymapRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
S
Sandeep Somavarapu 已提交
671
		const value = query.value.replace(/@recommended:keymaps/g, '').trim().toLowerCase();
S
Sandeep Somavarapu 已提交
672 673
		const names: string[] = this.tipsService.getKeymapRecommendations().map(({ extensionId }) => extensionId)
			.filter(extensionId => extensionId.toLowerCase().indexOf(value) > -1);
S
Sandeep Somavarapu 已提交
674 675

		if (!names.length) {
676
			return Promise.resolve(new PagedModel([]));
S
Sandeep Somavarapu 已提交
677
		}
678
		options.source = 'recommendations-keymaps';
S
Sandeep Somavarapu 已提交
679
		return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token)
680
			.then(result => this.getPagedModel(result));
S
Sandeep Somavarapu 已提交
681 682
	}

A
Anuj 已提交
683
	// Sorts the firstPage of the pager in the same order as given array of extension ids
684
	private sortFirstPage(pager: IPager<IExtension>, ids: string[]) {
685
		ids = ids.map(x => x.toLowerCase());
686
		pager.firstPage.sort((a, b) => {
S
Sandeep Somavarapu 已提交
687
			return ids.indexOf(a.identifier.id.toLowerCase()) < ids.indexOf(b.identifier.id.toLowerCase()) ? -1 : 1;
688
		});
689 690
	}

S
Sandeep Somavarapu 已提交
691
	private setModel(model: IPagedModel<IExtension>, error?: any) {
S
Sandeep Somavarapu 已提交
692
		if (this.list) {
J
Joao Moreno 已提交
693
			this.list.model = new DelayedPagedModel(model);
S
Sandeep Somavarapu 已提交
694 695 696 697
			this.list.scrollTop = 0;
			const count = this.count();

			toggleClass(this.extensionsList, 'hidden', count === 0);
S
Sandeep Somavarapu 已提交
698
			toggleClass(this.messageContainer, 'hidden', count > 0);
S
Sandeep Somavarapu 已提交
699 700
			this.badge.setCount(count);

701
			if (count === 0 && this.isBodyVisible()) {
S
Sandeep Somavarapu 已提交
702 703
				if (error) {
					if (error instanceof ExtensionListViewWarning) {
704
						this.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning);
S
Sandeep Somavarapu 已提交
705 706
						this.messageBox.textContent = getErrorMessage(error);
					} else {
707
						this.messageSeverityIcon.className = SeverityIcon.className(Severity.Error);
S
Sandeep Somavarapu 已提交
708 709 710
						this.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error));
					}
				} else {
711
					this.messageSeverityIcon.className = '';
S
Sandeep Somavarapu 已提交
712
					this.messageBox.textContent = localize('no extensions found', "No extensions found.");
713
				}
S
Sandeep Somavarapu 已提交
714
				alert(this.messageBox.textContent);
S
Sandeep Somavarapu 已提交
715
			}
S
Sandeep Somavarapu 已提交
716 717 718 719
		}
	}

	private openExtension(extension: IExtension): void {
S
Sandeep Somavarapu 已提交
720
		extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0] || extension;
R
Rob Lourens 已提交
721
		this.extensionsWorkbenchService.open(extension).then(undefined, err => this.onError(err));
S
Sandeep Somavarapu 已提交
722 723 724
	}

	private pin(): void {
B
Benjamin Pasero 已提交
725 726
		const activeControl = this.editorService.activeControl;
		if (activeControl) {
727
			activeControl.group.pinEditor(activeControl.input);
B
Benjamin Pasero 已提交
728 729
			activeControl.focus();
		}
S
Sandeep Somavarapu 已提交
730 731 732 733 734 735 736 737 738 739
	}

	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

		const message = err && err.message || '';

		if (/ECONNREFUSED/.test(message)) {
A
Alex Dima 已提交
740
			const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
S
Sandeep Somavarapu 已提交
741
				actions: [
742
					this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL)
S
Sandeep Somavarapu 已提交
743 744 745
				]
			});

746
			this.notificationService.error(error);
S
Sandeep Somavarapu 已提交
747 748 749
			return;
		}

750
		this.notificationService.error(err);
S
Sandeep Somavarapu 已提交
751 752
	}

S
Sandeep Somavarapu 已提交
753
	private getPagedModel(arg: IPager<IExtension> | IExtension[]): IPagedModel<IExtension> {
754
		if (Array.isArray(arg)) {
S
Sandeep Somavarapu 已提交
755
			return new PagedModel(arg);
756 757 758 759
		}
		const pager = {
			total: arg.total,
			pageSize: arg.pageSize,
S
Sandeep Somavarapu 已提交
760 761
			firstPage: arg.firstPage,
			getPage: (pageIndex: number, cancellationToken: CancellationToken) => arg.getPage(pageIndex, cancellationToken)
762 763 764 765
		};
		return new PagedModel(pager);
	}

S
Sandeep Somavarapu 已提交
766 767
	dispose(): void {
		super.dispose();
S
Sandeep Somavarapu 已提交
768 769 770 771
		if (this.queryRequest) {
			this.queryRequest.request.cancel();
			this.queryRequest = null;
		}
S
Sandeep Somavarapu 已提交
772
		this.list = null;
S
Sandeep Somavarapu 已提交
773 774
	}

S
Sandeep Somavarapu 已提交
775
	static isBuiltInExtensionsQuery(query: string): boolean {
776
		return /^\s*@builtin\s*$/i.test(query);
S
Sandeep Somavarapu 已提交
777 778
	}

779 780 781 782 783 784 785 786
	static isLocalExtensionsQuery(query: string): boolean {
		return this.isInstalledExtensionsQuery(query)
			|| this.isOutdatedExtensionsQuery(query)
			|| this.isEnabledExtensionsQuery(query)
			|| this.isDisabledExtensionsQuery(query)
			|| this.isBuiltInExtensionsQuery(query);
	}

S
Sandeep Somavarapu 已提交
787
	static isInstalledExtensionsQuery(query: string): boolean {
788 789 790 791 792
		return /@installed/i.test(query);
	}

	static isOutdatedExtensionsQuery(query: string): boolean {
		return /@outdated/i.test(query);
S
Sandeep Somavarapu 已提交
793 794
	}

795 796 797 798 799 800
	static isEnabledExtensionsQuery(query: string): boolean {
		return /@enabled/i.test(query);
	}

	static isDisabledExtensionsQuery(query: string): boolean {
		return /@disabled/i.test(query);
801 802
	}

S
Sandeep Somavarapu 已提交
803
	static isRecommendedExtensionsQuery(query: string): boolean {
804 805 806 807 808
		return /^@recommended$/i.test(query.trim());
	}

	static isSearchRecommendedExtensionsQuery(query: string): boolean {
		return /@recommended/i.test(query) && !ExtensionsListView.isRecommendedExtensionsQuery(query);
S
Sandeep Somavarapu 已提交
809 810 811 812 813 814 815 816 817
	}

	static isWorkspaceRecommendedExtensionsQuery(query: string): boolean {
		return /@recommended:workspace/i.test(query);
	}

	static isKeymapsRecommendedExtensionsQuery(query: string): boolean {
		return /@recommended:keymaps/i.test(query);
	}
818 819 820

	focus(): void {
		super.focus();
821 822 823 824
		if (!this.list) {
			return;
		}

825 826 827 828 829
		if (!(this.list.getFocus().length || this.list.getSelection().length)) {
			this.list.focusNext();
		}
		this.list.domFocus();
	}
S
Sandeep Somavarapu 已提交
830 831
}

832 833 834 835
export class ServerExtensionsView extends ExtensionsListView {

	constructor(
		server: IExtensionManagementServer,
836
		onDidChangeTitle: Event<string>,
837
		options: ExtensionsListViewOptions,
838 839 840 841 842 843 844 845 846 847 848 849 850 851
		@INotificationService notificationService: INotificationService,
		@IKeybindingService keybindingService: IKeybindingService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IThemeService themeService: IThemeService,
		@IExtensionService extensionService: IExtensionService,
		@IEditorService editorService: IEditorService,
		@IExtensionTipsService tipsService: IExtensionTipsService,
		@ITelemetryService telemetryService: ITelemetryService,
		@IConfigurationService configurationService: IConfigurationService,
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@IExperimentService experimentService: IExperimentService,
		@IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService,
		@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
A
Alex Dima 已提交
852 853
		@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
		@IProductService productService: IProductService,
S
Sandeep Somavarapu 已提交
854
		@IContextKeyService contextKeyService: IContextKeyService,
855
	) {
856
		options.server = server;
S
Sandeep Somavarapu 已提交
857
		super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService);
M
Matt Bierner 已提交
858
		this._register(onDidChangeTitle(title => this.updateTitle(title)));
859
	}
860

861
	async show(query: string): Promise<IPagedModel<IExtension>> {
862
		query = query ? query : '@installed';
863
		if (!ExtensionsListView.isLocalExtensionsQuery(query) && !ExtensionsListView.isBuiltInExtensionsQuery(query)) {
864 865 866 867 868 869
			query = query += ' @installed';
		}
		return super.show(query.trim());
	}
}

870 871
export class EnabledExtensionsView extends ExtensionsListView {

872
	async show(query: string): Promise<IPagedModel<IExtension>> {
S
Sandeep Somavarapu 已提交
873 874
		query = query || '@enabled';
		return ExtensionsListView.isEnabledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
875 876 877 878 879
	}
}

export class DisabledExtensionsView extends ExtensionsListView {

880
	async show(query: string): Promise<IPagedModel<IExtension>> {
S
Sandeep Somavarapu 已提交
881 882
		query = query || '@disabled';
		return ExtensionsListView.isDisabledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
883 884 885
	}
}

S
Sandeep Somavarapu 已提交
886
export class BuiltInExtensionsView extends ExtensionsListView {
887
	async show(query: string): Promise<IPagedModel<IExtension>> {
888
		return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:features');
S
Sandeep Somavarapu 已提交
889 890 891
	}
}

892
export class BuiltInThemesExtensionsView extends ExtensionsListView {
893
	async show(query: string): Promise<IPagedModel<IExtension>> {
894
		return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:themes');
895 896 897 898
	}
}

export class BuiltInBasicsExtensionsView extends ExtensionsListView {
899
	async show(query: string): Promise<IPagedModel<IExtension>> {
900
		return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:basics');
901 902 903
	}
}

904
export class DefaultRecommendedExtensionsView extends ExtensionsListView {
905
	private readonly recommendedExtensionsQuery = '@recommended:all';
906 907 908 909

	renderBody(container: HTMLElement): void {
		super.renderBody(container);

M
Matt Bierner 已提交
910
		this._register(this.tipsService.onRecommendationChange(() => {
911 912 913 914
			this.show('');
		}));
	}

915
	async show(query: string): Promise<IPagedModel<IExtension>> {
916 917 918 919
		if (query && query.trim() !== this.recommendedExtensionsQuery) {
			return this.showEmptyModel();
		}
		const model = await super.show(this.recommendedExtensionsQuery);
920
		if (!this.extensionsWorkbenchService.local.some(e => e.type === ExtensionType.User)) {
921 922 923 924
			// This is part of popular extensions view. Collapse if no installed extensions.
			this.setExpanded(model.length > 0);
		}
		return model;
925
	}
926

927 928
}

S
Sandeep Somavarapu 已提交
929
export class RecommendedExtensionsView extends ExtensionsListView {
930
	private readonly recommendedExtensionsQuery = '@recommended';
931 932 933 934

	renderBody(container: HTMLElement): void {
		super.renderBody(container);

M
Matt Bierner 已提交
935
		this._register(this.tipsService.onRecommendationChange(() => {
936 937 938 939
			this.show('');
		}));
	}

940
	async show(query: string): Promise<IPagedModel<IExtension>> {
941
		return (query && query.trim() !== this.recommendedExtensionsQuery) ? this.showEmptyModel() : super.show(this.recommendedExtensionsQuery);
S
Sandeep Somavarapu 已提交
942
	}
943 944 945
}

export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
946
	private readonly recommendedExtensionsQuery = '@recommended:workspace';
S
Sandeep Somavarapu 已提交
947
	private installAllAction: InstallWorkspaceRecommendedExtensionsAction;
948 949 950 951

	renderBody(container: HTMLElement): void {
		super.renderBody(container);

M
Matt Bierner 已提交
952 953 954
		this._register(this.tipsService.onRecommendationChange(() => this.update()));
		this._register(this.extensionsWorkbenchService.onChange(() => this.setRecommendationsToInstall()));
		this._register(this.contextService.onDidChangeWorkbenchState(() => this.update()));
955 956
	}

957 958 959 960 961 962
	renderHeader(container: HTMLElement): void {
		super.renderHeader(container);

		const listActionBar = $('.list-actionbar-container');
		container.insertBefore(listActionBar, this.badgeContainer);

M
Matt Bierner 已提交
963
		const actionbar = this._register(new ActionBar(listActionBar, {
964
			animated: false
M
Matt Bierner 已提交
965
		}));
966
		actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
S
Sandeep Somavarapu 已提交
967

M
Matt Bierner 已提交
968 969
		this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, []));
		const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL));
970

S
Sandeep Somavarapu 已提交
971
		this.installAllAction.class = 'octicon octicon-cloud-download';
972 973
		configureWorkspaceFolderAction.class = 'octicon octicon-pencil';

S
Sandeep Somavarapu 已提交
974
		actionbar.push([this.installAllAction], { icon: true, label: false });
975 976 977
		actionbar.push([configureWorkspaceFolderAction], { icon: true, label: false });
	}

978
	async show(query: string): Promise<IPagedModel<IExtension>> {
979 980
		let shouldShowEmptyView = query && query.trim() !== '@recommended' && query.trim() !== '@recommended:workspace';
		let model = await (shouldShowEmptyView ? this.showEmptyModel() : super.show(this.recommendedExtensionsQuery));
981 982
		this.setExpanded(model.length > 0);
		return model;
S
Sandeep Somavarapu 已提交
983 984
	}

S
Sandeep Somavarapu 已提交
985
	private update(): void {
986
		this.show(this.recommendedExtensionsQuery);
S
Sandeep Somavarapu 已提交
987 988 989
		this.setRecommendationsToInstall();
	}

990
	private setRecommendationsToInstall(): Promise<void> {
S
Sandeep Somavarapu 已提交
991 992 993 994
		return this.getRecommendationsToInstall()
			.then(recommendations => { this.installAllAction.recommendations = recommendations; });
	}

995
	private getRecommendationsToInstall(): Promise<IExtensionRecommendation[]> {
S
Sandeep Somavarapu 已提交
996
		return this.tipsService.getWorkspaceRecommendations()
997 998 999 1000 1001
			.then(recommendations => recommendations.filter(({ extensionId }) => {
				const extension = this.extensionsWorkbenchService.local.filter(i => areSameExtensions({ id: extensionId }, i.identifier))[0];
				if (!extension || !extension.local || extension.state !== ExtensionState.Installed) {
					return true;
				}
A
Alex Dima 已提交
1002
				return isUIExtension(extension.local.manifest, this.productService, this.configurationService) ? extension.server !== this.extensionManagementServerService.localExtensionManagementServer : extension.server !== this.extensionManagementServerService.remoteExtensionManagementServer;
1003
			}));
S
Sandeep Somavarapu 已提交
1004
	}
1005
}