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

J
Joao Moreno 已提交
6
import 'vs/css!./media/extensionsViewlet';
J
Joao Moreno 已提交
7
import { localize } from 'vs/nls';
S
Sandeep Somavarapu 已提交
8
import { ThrottledDelayer, timeout } from 'vs/base/common/async';
A
Alex Dima 已提交
9
import { isPromiseCanceledError } from 'vs/base/common/errors';
J
Joao Moreno 已提交
10
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
J
Joao Moreno 已提交
11
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
12
import { Event as EventOf, Emitter } from 'vs/base/common/event';
13
import { IAction } from 'vs/base/common/actions';
J
Joao Moreno 已提交
14
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
15
import { IViewlet } from 'vs/workbench/common/viewlet';
B
Benjamin Pasero 已提交
16
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
17
import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom';
J
Joao Moreno 已提交
18
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
J
Joao Moreno 已提交
19
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
20
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
21
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, ExtensionState, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey, VIEW_CONTAINER } from '../common/extensions';
22
import {
23
	ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction,
S
Sandeep Somavarapu 已提交
24
	ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
25 26
	EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, ChangeGroupAction
} from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
27
import { LocalExtensionType, IExtensionManagementService, IExtensionManagementServerService, IExtensionManagementServer, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
28
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
29
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, GroupByServerExtensionsView, DefaultRecommendedExtensionsView } from './extensionsViews';
30
import { OpenGlobalSettingsAction } from 'vs/workbench/parts/preferences/browser/preferencesActions';
S
Sandeep Somavarapu 已提交
31
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
32
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
J
Joao Moreno 已提交
33
import Severity from 'vs/base/common/severity';
I
isidor 已提交
34
import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
B
Benjamin Pasero 已提交
35
import { IThemeService } from 'vs/platform/theme/common/themeService';
S
Sandeep Somavarapu 已提交
36
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
37
import { ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views';
38
import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
S
Sandeep Somavarapu 已提交
39
import { IStorageService } from 'vs/platform/storage/common/storage';
40
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
S
Sandeep Somavarapu 已提交
41 42
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
J
Joao Moreno 已提交
43
import { getGalleryExtensionIdFromLocal, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
44
import { ILogService } from 'vs/platform/log/common/log';
45 46
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IWindowService } from 'vs/platform/windows/common/windows';
S
Sandeep Somavarapu 已提交
47
import { IPartService } from 'vs/workbench/services/part/common/partService';
48 49
import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
50 51 52
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
53
import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery';
54
import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput';
55
import { alert } from 'vs/base/browser/ui/aria/aria';
A
Alex Dima 已提交
56
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
57
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
J
Joao Moreno 已提交
58

59 60 61 62
interface SearchInputEvent extends Event {
	target: HTMLInputElement;
	immediate?: boolean;
}
J
Joao Moreno 已提交
63

64
const NonEmptyWorkspaceContext = new RawContextKey<boolean>('nonEmptyWorkspace', false);
S
Sandeep Somavarapu 已提交
65
const SearchExtensionsContext = new RawContextKey<boolean>('searchExtensions', false);
66
const HasInstalledExtensionsContext = new RawContextKey<boolean>('hasInstalledExtensions', true);
S
Sandeep Somavarapu 已提交
67
const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);
68
const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
69
const DefaultRecommendedExtensionsContext = new RawContextKey<boolean>('defaultRecommendedExtensions', false);
70
const GroupByServersContext = new RawContextKey<boolean>('groupByServersContext', false);
71 72 73 74 75 76 77 78 79 80 81 82
const viewIdNameMappings: { [id: string]: string } = {
	'extensions.listView': localize('marketPlace', "Marketplace"),
	'extensions.enabledExtensionList': localize('enabledExtensions', "Enabled"),
	'extensions.disabledExtensionList': localize('disabledExtensions', "Disabled"),
	'extensions.popularExtensionsList': localize('popularExtensions', "Popular"),
	'extensions.recommendedList': localize('recommendedExtensions', "Recommended"),
	'extensions.otherrecommendedList': localize('otherRecommendedExtensions', "Other Recommendations"),
	'extensions.workspaceRecommendedList': localize('workspaceRecommendedExtensions', "Workspace Recommendations"),
	'extensions.builtInExtensionsList': localize('builtInExtensions', "Features"),
	'extensions.builtInThemesExtensionsList': localize('builtInThemesExtensions', "Themes"),
	'extensions.builtInBasicsExtensionsList': localize('builtInBasicsExtensions', "Programming Languages"),
};
S
Sandeep Somavarapu 已提交
83

84
export class ExtensionsViewletViewsContribution implements IWorkbenchContribution {
J
Joao Moreno 已提交
85

J
Joao Moreno 已提交
86
	constructor(
87
		@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService
J
Joao Moreno 已提交
88
	) {
S
Sandeep Somavarapu 已提交
89
		this.registerViews();
J
Joao Moreno 已提交
90 91
	}

S
Sandeep Somavarapu 已提交
92
	private registerViews(): void {
M
Matt Bierner 已提交
93
		let viewDescriptors: IViewDescriptor[] = [];
94
		viewDescriptors.push(this.createMarketPlaceExtensionsListViewDescriptor());
95 96
		viewDescriptors.push(this.createEnabledExtensionsListViewDescriptor());
		viewDescriptors.push(this.createDisabledExtensionsListViewDescriptor());
97
		viewDescriptors.push(this.createPopularExtensionsListViewDescriptor());
98 99 100
		viewDescriptors.push(this.createBuiltInExtensionsListViewDescriptor());
		viewDescriptors.push(this.createBuiltInBasicsExtensionsListViewDescriptor());
		viewDescriptors.push(this.createBuiltInThemesExtensionsListViewDescriptor());
101 102 103
		viewDescriptors.push(this.createDefaultRecommendedExtensionsListViewDescriptor());
		viewDescriptors.push(this.createOtherRecommendedExtensionsListViewDescriptor());
		viewDescriptors.push(this.createWorkspaceRecommendedExtensionsListViewDescriptor());
104

S
Rename  
Sandeep Somavarapu 已提交
105
		if (this.extensionManagementServerService.remoteExtensionManagementServer) {
S
Sandeep Somavarapu 已提交
106
			viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.localExtensionManagementServer));
S
Rename  
Sandeep Somavarapu 已提交
107
			viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.remoteExtensionManagementServer));
108 109
		}

S
Sandeep Somavarapu 已提交
110 111 112
		ViewsRegistry.registerViews(viewDescriptors);
	}

113
	// View used for any kind of searching
114
	private createMarketPlaceExtensionsListViewDescriptor(): IViewDescriptor {
115
		const id = 'extensions.listView';
S
Sandeep Somavarapu 已提交
116
		return {
117 118
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
119
			container: VIEW_CONTAINER,
S
Sandeep Somavarapu 已提交
120
			ctor: ExtensionsListView,
121
			when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions'), ContextKeyExpr.not('searchBuiltInExtensions'), ContextKeyExpr.not('recommendedExtensions'), ContextKeyExpr.not('groupByServersContext')),
S
Sandeep Somavarapu 已提交
122
			weight: 100
S
Sandeep Somavarapu 已提交
123 124 125
		};
	}

126 127
	// Separate view for enabled extensions required as we need to show enabled, disabled and recommended sections
	// in the default view when there is no search text, but user has installed extensions.
128
	private createEnabledExtensionsListViewDescriptor(): IViewDescriptor {
129
		const id = 'extensions.enabledExtensionList';
130
		return {
131 132
			id,
			name: viewIdNameMappings[id],
133 134
			container: VIEW_CONTAINER,
			ctor: EnabledExtensionsView,
135
			when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('hasInstalledExtensions')),
136
			weight: 40,
137
			canToggleVisibility: true,
138
			order: 1
139 140 141
		};
	}

142 143
	// Separate view for disabled extensions required as we need to show enabled, disabled and recommended sections
	// in the default view when there is no search text, but user has installed extensions.
144
	private createDisabledExtensionsListViewDescriptor(): IViewDescriptor {
145
		const id = 'extensions.disabledExtensionList';
146
		return {
147 148
			id,
			name: viewIdNameMappings[id],
149 150
			container: VIEW_CONTAINER,
			ctor: DisabledExtensionsView,
151
			when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('hasInstalledExtensions')),
152
			weight: 10,
153
			canToggleVisibility: true,
154
			order: 3,
155
			collapsed: true
156 157 158
		};
	}

159 160
	// Separate view for popular extensions required as we need to show popular and recommended sections
	// in the default view when there is no search text, and user has no installed extensions.
161
	private createPopularExtensionsListViewDescriptor(): IViewDescriptor {
162
		const id = 'extensions.popularExtensionsList';
S
Sandeep Somavarapu 已提交
163
		return {
164 165
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
166
			container: VIEW_CONTAINER,
167 168 169 170
			ctor: ExtensionsListView,
			when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.not('hasInstalledExtensions')),
			weight: 60,
			order: 1
S
Sandeep Somavarapu 已提交
171 172 173
		};
	}

174 175
	private createExtensionsViewDescriptorsForServer(server: IExtensionManagementServer): IViewDescriptor[] {
		return [{
176 177
			id: `server.extensionsList.${server.authority}`,
			name: server.label,
178 179 180 181 182 183 184
			container: VIEW_CONTAINER,
			ctor: GroupByServerExtensionsView,
			when: ContextKeyExpr.has('groupByServersContext'),
			weight: 100
		}];
	}

185 186 187
	// Separate view for recommended extensions required as we need to show it along with other views when there is no search text.
	// When user has installed extensions, this is shown along with the views for enabled & disabled extensions
	// When user has no installed extensions, this is shown along with the view for popular extensions
188
	private createDefaultRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
189
		const id = 'extensions.recommendedList';
S
Sandeep Somavarapu 已提交
190
		return {
191 192
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
193
			container: VIEW_CONTAINER,
194
			ctor: DefaultRecommendedExtensionsView,
S
Sandeep Somavarapu 已提交
195
			when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('defaultRecommendedExtensions')),
196
			weight: 40,
197
			order: 2,
198
			canToggleVisibility: true
199 200 201
		};
	}

202 203
	// Separate view for recommedations that are not workspace recommendations.
	// Shown along with view for workspace recommendations, when using the command that shows recommendations
204
	private createOtherRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
205
		const id = 'extensions.otherrecommendedList';
206
		return {
207 208
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
209
			container: VIEW_CONTAINER,
210
			ctor: RecommendedExtensionsView,
211
			when: ContextKeyExpr.has('recommendedExtensions'),
S
Sandeep Somavarapu 已提交
212
			weight: 50,
213 214 215 216 217
			canToggleVisibility: true,
			order: 2
		};
	}

218 219
	// Separate view for workspace recommendations.
	// Shown along with view for other recommendations, when using the command that shows recommendations
220
	private createWorkspaceRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
221
		const id = 'extensions.workspaceRecommendedList';
222
		return {
223 224
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
225
			container: VIEW_CONTAINER,
226
			ctor: WorkspaceRecommendedExtensionsView,
S
Sandeep Somavarapu 已提交
227
			when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')),
S
Sandeep Somavarapu 已提交
228
			weight: 50,
229 230
			canToggleVisibility: true,
			order: 1
231 232 233
		};
	}

234
	private createBuiltInExtensionsListViewDescriptor(): IViewDescriptor {
235
		const id = 'extensions.builtInExtensionsList';
S
Sandeep Somavarapu 已提交
236
		return {
237 238
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
239
			container: VIEW_CONTAINER,
S
Sandeep Somavarapu 已提交
240
			ctor: BuiltInExtensionsView,
241
			when: ContextKeyExpr.has('searchBuiltInExtensions'),
242 243 244 245 246
			weight: 100,
			canToggleVisibility: true
		};
	}

247
	private createBuiltInThemesExtensionsListViewDescriptor(): IViewDescriptor {
248
		const id = 'extensions.builtInThemesExtensionsList';
249
		return {
250 251
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
252
			container: VIEW_CONTAINER,
253
			ctor: BuiltInThemesExtensionsView,
254
			when: ContextKeyExpr.has('searchBuiltInExtensions'),
255 256 257 258 259
			weight: 100,
			canToggleVisibility: true
		};
	}

260
	private createBuiltInBasicsExtensionsListViewDescriptor(): IViewDescriptor {
261
		const id = 'extensions.builtInBasicsExtensionsList';
262
		return {
263 264
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
265
			container: VIEW_CONTAINER,
266
			ctor: BuiltInBasicsExtensionsView,
267
			when: ContextKeyExpr.has('searchBuiltInExtensions'),
268 269
			weight: 100,
			canToggleVisibility: true
270 271
		};
	}
272 273
}

S
Sandeep Somavarapu 已提交
274
export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensionsViewlet {
275 276 277 278

	private onSearchChange: EventOf<string>;
	private nonEmptyWorkspaceContextKey: IContextKey<boolean>;
	private searchExtensionsContextKey: IContextKey<boolean>;
279
	private hasInstalledExtensionsContextKey: IContextKey<boolean>;
280
	private searchBuiltInExtensionsContextKey: IContextKey<boolean>;
281
	private groupByServersContextKey: IContextKey<boolean>;
282 283 284 285 286 287
	private recommendedExtensionsContextKey: IContextKey<boolean>;
	private defaultRecommendedExtensionsContextKey: IContextKey<boolean>;

	private searchDelayer: ThrottledDelayer<any>;
	private root: HTMLElement;

288
	private searchBox: SuggestEnabledInput;
289 290 291
	private extensionsBox: HTMLElement;
	private primaryActions: IAction[];
	private secondaryActions: IAction[];
292
	private groupByServerAction: IAction;
293 294 295 296 297
	private disposables: IDisposable[] = [];

	constructor(
		@IPartService partService: IPartService,
		@ITelemetryService telemetryService: ITelemetryService,
S
Sandeep Somavarapu 已提交
298
		@IProgressService2 private progressService: IProgressService2,
299
		@IInstantiationService instantiationService: IInstantiationService,
300
		@IEditorGroupsService private editorGroupService: IEditorGroupsService,
301 302 303 304
		@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
		@INotificationService private notificationService: INotificationService,
		@IViewletService private viewletService: IViewletService,
		@IThemeService themeService: IThemeService,
305
		@IConfigurationService configurationService: IConfigurationService,
306 307 308 309
		@IStorageService storageService: IStorageService,
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@IContextKeyService contextKeyService: IContextKeyService,
		@IContextMenuService contextMenuService: IContextMenuService,
310
		@IExtensionService extensionService: IExtensionService,
311
		@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService
312
	) {
313
		super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
314 315 316 317

		this.searchDelayer = new ThrottledDelayer(500);
		this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService);
		this.searchExtensionsContextKey = SearchExtensionsContext.bindTo(contextKeyService);
318
		this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService);
319 320
		this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);
		this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);
321
		this.groupByServersContextKey = GroupByServersContext.bindTo(contextKeyService);
322 323 324 325
		this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService);
		this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
		this.disposables.push(this.viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables));

326 327 328 329
		this.extensionManagementService.getInstalled(LocalExtensionType.User).then(result => {
			this.hasInstalledExtensionsContextKey.set(result.length > 0);
		});

330 331 332 333 334 335 336 337 338 339
		this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {
				this.secondaryActions = null;
				this.updateTitleArea();
			}
			if (e.affectedKeys.indexOf(ShowRecommendationsOnlyOnDemandKey) > -1) {
				this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
			}
		}, this, this.disposables);
	}
340

I
isidor 已提交
341
	create(parent: HTMLElement): void {
342 343
		addClass(parent, 'extensions-viewlet');
		this.root = parent;
J
Joao Moreno 已提交
344

J
Joao Moreno 已提交
345
		const header = append(this.root, $('.header'));
346

347
		const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
J
Joao Moreno 已提交
348

349 350 351 352 353 354 355 356 357 358 359
		this.searchBox = this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, header, {
			triggerCharacters: ['@'],
			sortKey: item => {
				if (item.indexOf(':') === -1) { return 'a'; }
				else if (/ext:/.test(item) || /tag:/.test(item)) { return 'b'; }
				else if (/sort:/.test(item)) { return 'c'; }
				else { return 'd'; }
			},
			provideResults: (query) => Query.suggestions(query)
		}, placeholder, 'extensions:searchinput', { placeholderText: placeholder });

360 361
		this.disposables.push(attachSuggestEnabledInputBoxStyler(this.searchBox, this.themeService));

362 363 364 365 366
		this.disposables.push(this.searchBox);

		const _searchChange = new Emitter<string>();
		this.onSearchChange = _searchChange.event;
		this.searchBox.onInputDidChange(() => {
367
			this.triggerSearch();
368 369 370 371
			_searchChange.fire(this.searchBox.getValue());
		}, this, this.disposables);

		this.searchBox.onShouldFocusResults(() => this.focusListView(), this, this.disposables);
J
Joao Moreno 已提交
372

373
		this._register(this.onDidChangeVisibility(visible => {
374 375
			if (visible) {
				this.searchBox.focus();
376
			}
377 378 379 380
		}));

		this.extensionsBox = append(this.root, $('.extensions'));
		super.create(this.extensionsBox);
J
Joao Moreno 已提交
381 382 383
	}

	focus(): void {
J
Joao Moreno 已提交
384
		this.searchBox.focus();
J
Joao Moreno 已提交
385 386
	}

S
Sandeep Somavarapu 已提交
387 388
	layout(dimension: Dimension): void {
		toggleClass(this.root, 'narrow', dimension.width <= 300);
389
		this.searchBox.layout({ height: 20, width: dimension.width - 34 });
S
Sandeep Somavarapu 已提交
390
		super.layout(new Dimension(dimension.width, dimension.height - 38));
J
Joao Moreno 已提交
391 392 393 394
	}

	getOptimalWidth(): number {
		return 400;
J
Joao Moreno 已提交
395 396
	}

J
Joao Moreno 已提交
397
	getActions(): IAction[] {
J
Joao Moreno 已提交
398 399 400 401
		if (!this.primaryActions) {
			this.primaryActions = [
				this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange)
			];
402
		}
J
Joao Moreno 已提交
403
		return this.primaryActions;
J
Joao Moreno 已提交
404 405
	}

406
	getSecondaryActions(): IAction[] {
J
Joao Moreno 已提交
407
		if (!this.secondaryActions) {
408 409 410
			if (!this.groupByServerAction) {
				this.groupByServerAction = this.instantiationService.createInstance(ChangeGroupAction, 'extensions.group.servers', localize('group by servers', "Group By: Server"), this.onSearchChange, 'server');
				this.disposables.push(this.onSearchChange(value => {
411
					this.groupByServerAction.enabled = !value || ExtensionsListView.isInstalledExtensionsQuery(value) || ExtensionsListView.isBuiltInExtensionsQuery(value);
412 413
				}));
			}
J
Joao Moreno 已提交
414
			this.secondaryActions = [
S
Sandeep Somavarapu 已提交
415
				this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL),
J
Joao Moreno 已提交
416
				this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL),
417
				this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL),
418
				this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL),
S
Sandeep Somavarapu 已提交
419
				this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL),
J
Joao Moreno 已提交
420
				this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL),
J
Joao Moreno 已提交
421 422
				this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL),
				new Separator(),
J
Joao Moreno 已提交
423 424 425
				this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Sort By: Install Count"), this.onSearchChange, 'installs'),
				this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Sort By: Rating"), this.onSearchChange, 'rating'),
				this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Sort By: Name"), this.onSearchChange, 'name'),
J
Joao Moreno 已提交
426
				new Separator(),
S
Rename  
Sandeep Somavarapu 已提交
427
				...(this.extensionManagementServerService.remoteExtensionManagementServer ? [this.groupByServerAction, new Separator()] : []),
J
Joao Moreno 已提交
428
				this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL),
429
				...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]),
S
Sandeep Somavarapu 已提交
430 431 432 433
				this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL),
				new Separator(),
				this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL),
				this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL)
J
Joao Moreno 已提交
434 435 436 437
			];
		}

		return this.secondaryActions;
438 439
	}

440
	search(value: string): void {
441
		const event = new Event('input', { bubbles: true }) as SearchInputEvent;
442
		event.immediate = true;
443

444
		this.searchBox.setValue(value);
J
Joao Moreno 已提交
445 446
	}

S
Sandeep Somavarapu 已提交
447
	private triggerSearch(immediate = false): void {
448
		this.searchDelayer.trigger(() => this.doSearch(), immediate || !this.searchBox.getValue() ? 0 : 500).then(void 0, err => this.onError(err));
449 450 451
	}

	private normalizedQuery(): string {
452
		return this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:');
J
Joao Moreno 已提交
453 454
	}

455
	private doSearch(): Promise<any> {
456
		const value = this.normalizedQuery();
S
Sandeep Somavarapu 已提交
457
		this.searchExtensionsContextKey.set(!!value);
S
Sandeep Somavarapu 已提交
458
		this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
459
		this.groupByServersContextKey.set(ExtensionsListView.isGroupByServersExtensionsQuery(value));
460
		this.recommendedExtensionsContextKey.set(ExtensionsListView.isRecommendedExtensionsQuery(value));
461
		this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY);
462

463
		if (value) {
S
Sandeep Somavarapu 已提交
464 465 466 467
			return this.progress(Promise.all(this.panels.map(view =>
				(<ExtensionsListView>view).show(this.normalizedQuery())
					.then(model => this.alertSearchResult(model.length, view.id))
			)));
468
		}
469
		return Promise.resolve(null);
470 471
	}

472 473
	protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] {
		const addedViews = super.onDidAddViews(added);
S
Sandeep Somavarapu 已提交
474 475 476 477
		this.progress(Promise.all(addedViews.map(addedView =>
			(<ExtensionsListView>addedView).show(this.normalizedQuery())
				.then(model => this.alertSearchResult(model.length, addedView.id))
		)));
478
		return addedViews;
479 480
	}

481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
	private alertSearchResult(count: number, viewId: string) {
		switch (count) {
			case 0:
				break;
			case 1:
				if (viewIdNameMappings[viewId]) {
					alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", viewIdNameMappings[viewId]));
				} else {
					alert(localize('extensionFound', "1 extension found."));
				}
				break;
			default:
				if (viewIdNameMappings[viewId]) {
					alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, viewIdNameMappings[viewId]));
				} else {
					alert(localize('extensionsFound', "{0} extensions found.", count));
				}
				break;
		}
	}

502
	protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel {
S
Rename  
Sandeep Somavarapu 已提交
503
		if (this.extensionManagementServerService.remoteExtensionManagementServer) {
S
Sandeep Somavarapu 已提交
504
			const extensionManagementServer = viewDescriptor.id === `server.extensionsList.${this.extensionManagementServerService.localExtensionManagementServer.authority}` ? this.extensionManagementServerService.localExtensionManagementServer
S
Rename  
Sandeep Somavarapu 已提交
505
				: viewDescriptor.id === `server.extensionsList.${this.extensionManagementServerService.remoteExtensionManagementServer.authority}` ? this.extensionManagementServerService.remoteExtensionManagementServer : null;
S
Sandeep Somavarapu 已提交
506
			if (extensionManagementServer) {
507
				const servicesCollection: ServiceCollection = new ServiceCollection();
S
Sandeep Somavarapu 已提交
508
				servicesCollection.set(IExtensionManagementService, extensionManagementServer.extensionManagementService);
509 510
				servicesCollection.set(IExtensionsWorkbenchService, new SyncDescriptor(ExtensionsWorkbenchService));
				const instantiationService = this.instantiationService.createChild(servicesCollection);
S
Sandeep Somavarapu 已提交
511
				return instantiationService.createInstance(viewDescriptor.ctor, options, [extensionManagementServer]) as ViewletPanel;
512 513 514 515 516
			}
		}
		return this.instantiationService.createInstance(viewDescriptor.ctor, options) as ViewletPanel;
	}

517 518
	private count(): number {
		return this.panels.reduce((count, view) => (<ExtensionsListView>view).count() + count, 0);
J
Joao Moreno 已提交
519 520
	}

521
	private focusListView(): void {
522 523 524
		if (this.count() > 0) {
			this.panels[0].focus();
		}
525 526
	}

527 528 529 530 531
	private onViewletOpen(viewlet: IViewlet): void {
		if (!viewlet || viewlet.getId() === VIEWLET_ID) {
			return;
		}

532 533 534 535
		if (this.configurationService.getValue<boolean>(CloseExtensionDetailsOnViewChangeKey)) {
			const promises = this.editorGroupService.groups.map(group => {
				const editors = group.editors.filter(input => input instanceof ExtensionsInput);
				const promises = editors.map(editor => group.closeEditor(editor));
536

537
				return Promise.all(promises);
538
			});
539

540
			Promise.all(promises);
541
		}
542 543
	}

544
	private progress<T>(promise: Promise<T>): Promise<T> {
S
Sandeep Somavarapu 已提交
545
		return this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => promise);
S
Sandeep Somavarapu 已提交
546 547
	}

J
Joao Moreno 已提交
548 549 550 551 552 553 554
	private onError(err: any): void {
		if (isPromiseCanceledError(err)) {
			return;
		}

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

J
Joao Moreno 已提交
555
		if (/ECONNREFUSED/.test(message)) {
A
Alex Dima 已提交
556
			const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
J
Joao Moreno 已提交
557
				actions: [
558
					this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL)
J
Joao Moreno 已提交
559 560 561
				]
			});

562
			this.notificationService.error(error);
J
Joao Moreno 已提交
563 564 565
			return;
		}

566
		this.notificationService.error(err);
J
Joao Moreno 已提交
567 568
	}

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

export class StatusUpdater implements IWorkbenchContribution {

	private disposables: IDisposable[];
578
	private badgeHandle: IDisposable;
J
Joao Moreno 已提交
579 580

	constructor(
I
isidor 已提交
581
		@IActivityService private activityService: IActivityService,
J
Joao Moreno 已提交
582 583 584 585 586 587
		@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
	) {
		extensionsWorkbenchService.onChange(this.onServiceChange, this, this.disposables);
	}

	private onServiceChange(): void {
588 589 590

		dispose(this.badgeHandle);

J
Joao Moreno 已提交
591
		if (this.extensionsWorkbenchService.local.some(e => e.state === ExtensionState.Installing)) {
I
isidor 已提交
592
			this.badgeHandle = this.activityService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', "Extensions")), 'extensions-badge progress-badge');
J
Joao Moreno 已提交
593 594 595
			return;
		}

596
		const outdated = this.extensionsWorkbenchService.local.reduce((r, e) => r + (e.outdated && e.enablementState !== EnablementState.Disabled && e.enablementState !== EnablementState.WorkspaceDisabled ? 1 : 0), 0);
597 598
		if (outdated > 0) {
			const badge = new NumberBadge(outdated, n => localize('outdatedExtensions', '{0} Outdated Extensions', n));
I
isidor 已提交
599
			this.badgeHandle = this.activityService.showActivity(VIEWLET_ID, badge, 'extensions-badge count-badge');
600
		}
J
Joao Moreno 已提交
601 602 603 604
	}

	dispose(): void {
		this.disposables = dispose(this.disposables);
605
		dispose(this.badgeHandle);
J
Joao Moreno 已提交
606 607
	}
}
608 609 610 611 612 613 614

export class MaliciousExtensionChecker implements IWorkbenchContribution {

	private disposables: IDisposable[];

	constructor(
		@IExtensionManagementService private extensionsManagementService: IExtensionManagementService,
615
		@IWindowService private windowService: IWindowService,
616
		@ILogService private logService: ILogService,
617 618
		@INotificationService private notificationService: INotificationService,
		@IEnvironmentService private environmentService: IEnvironmentService
619
	) {
620 621 622
		if (!this.environmentService.disableExtensions) {
			this.loopCheckForMaliciousExtensions();
		}
623 624 625 626
	}

	private loopCheckForMaliciousExtensions(): void {
		this.checkForMaliciousExtensions()
627
			.then(() => timeout(1000 * 60 * 5)) // every five minutes
628 629 630
			.then(() => this.loopCheckForMaliciousExtensions());
	}

631
	private checkForMaliciousExtensions(): Promise<any> {
J
Joao Moreno 已提交
632 633 634
		return this.extensionsManagementService.getExtensionsReport().then(report => {
			const maliciousSet = getMaliciousExtensionsSet(report);

635 636 637 638 639
			return this.extensionsManagementService.getInstalled(LocalExtensionType.User).then(installed => {
				const maliciousExtensions = installed
					.filter(e => maliciousSet.has(getGalleryExtensionIdFromLocal(e)));

				if (maliciousExtensions.length) {
640
					return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
641 642 643 644 645 646
						this.notificationService.prompt(
							Severity.Warning,
							localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", getGalleryExtensionIdFromLocal(e)),
							[{
								label: localize('reloadNow', "Reload Now"),
								run: () => this.windowService.reloadWindow()
B
Benjamin Pasero 已提交
647 648
							}],
							{ sticky: true }
649
						);
650 651
					})));
				} else {
652
					return Promise.resolve(null);
653 654 655 656 657 658 659 660
				}
			});
		}, err => this.logService.error(err));
	}

	dispose(): void {
		this.disposables = dispose(this.disposables);
	}
661
}