extensionsViewlet.ts 35.6 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.
 *--------------------------------------------------------------------------------------------*/

6
import 'vs/css!./media/extensionsViewlet';
J
Joao Moreno 已提交
7
import { localize } from 'vs/nls';
S
Sandeep Somavarapu 已提交
8
import { timeout, Delayer } 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';
11
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
12
import { Event as EventOf, Emitter } from 'vs/base/common/event';
S
Sandeep Somavarapu 已提交
13
import { IAction, Action } 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, hide, show } 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, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions';
22
import {
23
	ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction,
S
Sandeep Somavarapu 已提交
24
	ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
S
Sandeep Somavarapu 已提交
25
	EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction
26
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
27
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
S
rename  
Sandeep Somavarapu 已提交
28
import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
29
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
S
Sandeep Somavarapu 已提交
30
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews';
31
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
32
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
J
Joao Moreno 已提交
33
import Severity from 'vs/base/common/severity';
S
Sandeep Somavarapu 已提交
34
import { IActivityService, 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';
37
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views';
38
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
39
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
S
Sandeep Somavarapu 已提交
40 41
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
42
import { getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
43
import { ILogService } from 'vs/platform/log/common/log';
44
import { INotificationService } from 'vs/platform/notification/common/notification';
45
import { IHostService } from 'vs/workbench/services/host/browser/host';
46
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
S
SteVen Batten 已提交
47
import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
48
import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery';
49
import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput';
50
import { alert } from 'vs/base/browser/ui/aria/aria';
A
Alex Dima 已提交
51
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
52
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
53
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
54
import { Registry } from 'vs/platform/registry/common/platform';
55
import { RemoteNameContext } from 'vs/workbench/browser/contextkeys';
56
import { ILabelService } from 'vs/platform/label/common/label';
57
import { MementoObject } from 'vs/workbench/common/memento';
S
Sandeep Somavarapu 已提交
58
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
S
Sandeep Somavarapu 已提交
59
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
60 61 62
import { DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { URI } from 'vs/base/common/uri';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
J
Joao Moreno 已提交
63

64
const NonEmptyWorkspaceContext = new RawContextKey<boolean>('nonEmptyWorkspace', false);
65 66
const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);
67 68 69 70
const SearchIntalledExtensionsContext = new RawContextKey<boolean>('searchInstalledExtensions', false);
const SearchOutdatedExtensionsContext = new RawContextKey<boolean>('searchOutdatedExtensions', false);
const SearchEnabledExtensionsContext = new RawContextKey<boolean>('searchEnabledExtensions', false);
const SearchDisabledExtensionsContext = new RawContextKey<boolean>('searchDisabledExtensions', false);
71
const HasInstalledExtensionsContext = new RawContextKey<boolean>('hasInstalledExtensions', true);
S
Sandeep Somavarapu 已提交
72
const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);
73
const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
74
const DefaultRecommendedExtensionsContext = new RawContextKey<boolean>('defaultRecommendedExtensions', false);
75 76 77
const viewIdNameMappings: { [id: string]: string } = {
	'extensions.listView': localize('marketPlace', "Marketplace"),
	'extensions.enabledExtensionList': localize('enabledExtensions', "Enabled"),
78
	'extensions.enabledExtensionList2': localize('enabledExtensions', "Enabled"),
79
	'extensions.disabledExtensionList': localize('disabledExtensions', "Disabled"),
80
	'extensions.disabledExtensionList2': localize('disabledExtensions', "Disabled"),
81 82 83 84 85 86 87
	'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"),
88
	'extensions.syncedExtensionsList': localize('syncedExtensions', "My Account"),
89
};
S
Sandeep Somavarapu 已提交
90

91
export class ExtensionsViewletViewsContribution implements IWorkbenchContribution {
J
Joao Moreno 已提交
92

93 94
	private readonly container: ViewContainer;

J
Joao Moreno 已提交
95
	constructor(
96 97
		@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
		@ILabelService private readonly labelService: ILabelService,
98
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService
J
Joao Moreno 已提交
99
	) {
100
		this.container = viewDescriptorService.getViewContainerById(VIEWLET_ID)!;
S
Sandeep Somavarapu 已提交
101
		this.registerViews();
J
Joao Moreno 已提交
102 103
	}

S
Sandeep Somavarapu 已提交
104
	private registerViews(): void {
M
Matt Bierner 已提交
105
		let viewDescriptors: IViewDescriptor[] = [];
106
		viewDescriptors.push(this.createMarketPlaceExtensionsListViewDescriptor());
107 108 109
		viewDescriptors.push(this.createDefaultEnabledExtensionsListViewDescriptor());
		viewDescriptors.push(this.createDefaultDisabledExtensionsListViewDescriptor());
		viewDescriptors.push(this.createDefaultPopularExtensionsListViewDescriptor());
110 111
		viewDescriptors.push(this.createEnabledExtensionsListViewDescriptor());
		viewDescriptors.push(this.createDisabledExtensionsListViewDescriptor());
112 113 114
		viewDescriptors.push(this.createBuiltInExtensionsListViewDescriptor());
		viewDescriptors.push(this.createBuiltInBasicsExtensionsListViewDescriptor());
		viewDescriptors.push(this.createBuiltInThemesExtensionsListViewDescriptor());
115 116 117
		viewDescriptors.push(this.createDefaultRecommendedExtensionsListViewDescriptor());
		viewDescriptors.push(this.createOtherRecommendedExtensionsListViewDescriptor());
		viewDescriptors.push(this.createWorkspaceRecommendedExtensionsListViewDescriptor());
118

119 120 121
		if (this.extensionManagementServerService.localExtensionManagementServer) {
			viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.localExtensionManagementServer));
		}
S
Rename  
Sandeep Somavarapu 已提交
122 123
		if (this.extensionManagementServerService.remoteExtensionManagementServer) {
			viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.remoteExtensionManagementServer));
124 125
		}

126
		Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).registerViews(viewDescriptors, this.container);
S
Sandeep Somavarapu 已提交
127 128
	}

129
	// View used for any kind of searching
130
	private createMarketPlaceExtensionsListViewDescriptor(): IViewDescriptor {
131
		const id = 'extensions.listView';
S
Sandeep Somavarapu 已提交
132
		return {
133 134
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
135
			ctorDescriptor: new SyncDescriptor(ExtensionsListView),
136
			when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')),
S
Sandeep Somavarapu 已提交
137
			weight: 100
S
Sandeep Somavarapu 已提交
138 139 140
		};
	}

141 142
	// 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.
143
	private createDefaultEnabledExtensionsListViewDescriptor(): IViewDescriptor {
144
		const id = 'extensions.enabledExtensionList';
145
		return {
146 147
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
148
			ctorDescriptor: new SyncDescriptor(EnabledExtensionsView),
149
			when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')),
150
			weight: 40,
151
			canToggleVisibility: true,
152
			order: 1
153 154 155
		};
	}

156 157
	// 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.
158
	private createDefaultDisabledExtensionsListViewDescriptor(): IViewDescriptor {
159
		const id = 'extensions.disabledExtensionList';
160
		return {
161 162
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
163
			ctorDescriptor: new SyncDescriptor(DisabledExtensionsView),
164
			when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')),
165
			weight: 10,
166
			canToggleVisibility: true,
167
			order: 3,
168
			collapsed: true
169 170 171
		};
	}

172 173
	// 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.
174
	private createDefaultPopularExtensionsListViewDescriptor(): IViewDescriptor {
175
		const id = 'extensions.popularExtensionsList';
S
Sandeep Somavarapu 已提交
176
		return {
177 178
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
179
			ctorDescriptor: new SyncDescriptor(ExtensionsListView),
180
			when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')),
181 182
			weight: 60,
			order: 1
S
Sandeep Somavarapu 已提交
183 184 185
		};
	}

186
	private createExtensionsViewDescriptorsForServer(server: IExtensionManagementServer): IViewDescriptor[] {
187
		const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => {
188 189
			const serverLabel = server.label;
			if (viewTitle && this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
190 191 192 193 194 195
				return `${serverLabel} - ${viewTitle}`;
			}
			return viewTitle ? viewTitle : serverLabel;
		};
		const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server);
		const getOutdatedViewName = (): string => getViewName(localize('outdated', "Outdated"), server);
196
		const onDidChangeServerLabel: EventOf<void> = EventOf.map(this.labelService.onDidChangeFormatters, () => undefined);
197
		return [{
198
			id: `extensions.${server.authority}.installed`,
199
			get name() { return getInstalledViewName(); },
S
Sandeep Somavarapu 已提交
200
			ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getInstalledViewName())]),
201 202 203 204
			when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')),
			weight: 100
		}, {
			id: `extensions.${server.authority}.outdated`,
205
			get name() { return getOutdatedViewName(); },
S
Sandeep Somavarapu 已提交
206
			ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getOutdatedViewName())]),
207
			when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')),
208 209 210
			weight: 100
		}, {
			id: `extensions.${server.authority}.default`,
211
			get name() { return getInstalledViewName(); },
S
Sandeep Somavarapu 已提交
212
			ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getInstalledViewName())]),
213
			when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.notEqualsTo('')),
214 215
			weight: 40,
			order: 1
216 217 218
		}];
	}

219 220 221
	// 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
222
	private createDefaultRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
223
		const id = 'extensions.recommendedList';
S
Sandeep Somavarapu 已提交
224
		return {
225 226
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
227
			ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView),
228
			when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('defaultRecommendedExtensions')),
229
			weight: 40,
230
			order: 2,
231
			canToggleVisibility: true
232 233 234
		};
	}

235 236
	// Separate view for recommedations that are not workspace recommendations.
	// Shown along with view for workspace recommendations, when using the command that shows recommendations
237
	private createOtherRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
238
		const id = 'extensions.otherrecommendedList';
239
		return {
240 241
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
242
			ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView),
243
			when: ContextKeyExpr.has('recommendedExtensions'),
S
Sandeep Somavarapu 已提交
244
			weight: 50,
245 246 247 248
			order: 2
		};
	}

249 250
	// Separate view for workspace recommendations.
	// Shown along with view for other recommendations, when using the command that shows recommendations
251
	private createWorkspaceRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
252
		const id = 'extensions.workspaceRecommendedList';
253
		return {
254 255
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
256
			ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView),
S
Sandeep Somavarapu 已提交
257
			when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')),
S
Sandeep Somavarapu 已提交
258
			weight: 50,
259
			order: 1
260 261 262
		};
	}

263 264 265 266 267
	private createEnabledExtensionsListViewDescriptor(): IViewDescriptor {
		const id = 'extensions.enabledExtensionList2';
		return {
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
268
			ctorDescriptor: new SyncDescriptor(EnabledExtensionsView),
269 270 271 272 273 274 275 276 277 278 279
			when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')),
			weight: 40,
			order: 1
		};
	}

	private createDisabledExtensionsListViewDescriptor(): IViewDescriptor {
		const id = 'extensions.disabledExtensionList2';
		return {
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
280
			ctorDescriptor: new SyncDescriptor(DisabledExtensionsView),
281 282 283 284 285 286 287
			when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')),
			weight: 10,
			order: 3,
			collapsed: true
		};
	}

288
	private createBuiltInExtensionsListViewDescriptor(): IViewDescriptor {
289
		const id = 'extensions.builtInExtensionsList';
S
Sandeep Somavarapu 已提交
290
		return {
291 292
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
293
			ctorDescriptor: new SyncDescriptor(BuiltInExtensionsView),
294
			when: ContextKeyExpr.has('searchBuiltInExtensions'),
295
			weight: 100
296 297 298
		};
	}

299
	private createBuiltInThemesExtensionsListViewDescriptor(): IViewDescriptor {
300
		const id = 'extensions.builtInThemesExtensionsList';
301
		return {
302 303
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
304
			ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView),
305
			when: ContextKeyExpr.has('searchBuiltInExtensions'),
306
			weight: 100
307 308 309
		};
	}

310
	private createBuiltInBasicsExtensionsListViewDescriptor(): IViewDescriptor {
311
		const id = 'extensions.builtInBasicsExtensionsList';
312
		return {
313 314
			id,
			name: viewIdNameMappings[id],
S
Sandeep Somavarapu 已提交
315
			ctorDescriptor: new SyncDescriptor(BuiltInBasicsExtensionsView),
316
			when: ContextKeyExpr.has('searchBuiltInExtensions'),
317
			weight: 100
318 319
		};
	}
320

321 322
}

S
SteVen Batten 已提交
323
export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer {
324

S
Sandeep Somavarapu 已提交
325 326
	private readonly _onSearchChange: Emitter<string> = this._register(new Emitter<string>());
	private readonly onSearchChange: EventOf<string> = this._onSearchChange.event;
327
	private nonEmptyWorkspaceContextKey: IContextKey<boolean>;
328 329
	private defaultViewsContextKey: IContextKey<boolean>;
	private searchMarketplaceExtensionsContextKey: IContextKey<boolean>;
330 331 332 333
	private searchInstalledExtensionsContextKey: IContextKey<boolean>;
	private searchOutdatedExtensionsContextKey: IContextKey<boolean>;
	private searchEnabledExtensionsContextKey: IContextKey<boolean>;
	private searchDisabledExtensionsContextKey: IContextKey<boolean>;
334
	private hasInstalledExtensionsContextKey: IContextKey<boolean>;
335 336 337 338
	private searchBuiltInExtensionsContextKey: IContextKey<boolean>;
	private recommendedExtensionsContextKey: IContextKey<boolean>;
	private defaultRecommendedExtensionsContextKey: IContextKey<boolean>;

S
Sandeep Somavarapu 已提交
339
	private searchDelayer: Delayer<void>;
S
Sandeep Somavarapu 已提交
340 341 342 343
	private root: HTMLElement | undefined;
	private searchBox: SuggestEnabledInput | undefined;
	private primaryActions: IAction[] | undefined;
	private secondaryActions: IAction[] | null = null;
344
	private readonly searchViewletState: MementoObject;
345 346

	constructor(
347
		@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
348
		@ITelemetryService telemetryService: ITelemetryService,
349
		@IProgressService private readonly progressService: IProgressService,
350
		@IInstantiationService instantiationService: IInstantiationService,
351 352 353 354
		@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
		@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
		@INotificationService private readonly notificationService: INotificationService,
		@IViewletService private readonly viewletService: IViewletService,
355
		@IThemeService themeService: IThemeService,
356
		@IConfigurationService configurationService: IConfigurationService,
357 358 359 360
		@IStorageService storageService: IStorageService,
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@IContextKeyService contextKeyService: IContextKeyService,
		@IContextMenuService contextMenuService: IContextMenuService,
361
		@IExtensionService extensionService: IExtensionService,
S
Sandeep Somavarapu 已提交
362 363
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
		@IPreferencesService private readonly preferencesService: IPreferencesService
364
	) {
365
		super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
366

S
Sandeep Somavarapu 已提交
367
		this.searchDelayer = new Delayer(500);
368
		this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService);
369 370
		this.defaultViewsContextKey = DefaultViewsContext.bindTo(contextKeyService);
		this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService);
371 372 373 374
		this.searchInstalledExtensionsContextKey = SearchIntalledExtensionsContext.bindTo(contextKeyService);
		this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService);
		this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService);
		this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService);
375
		this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService);
376 377 378 379
		this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);
		this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);
		this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService);
		this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
380
		this._register(this.viewletService.onDidViewletOpen(this.onViewletOpen, this));
381
		this.searchViewletState = this.getMemento(StorageScope.WORKSPACE);
382

383
		this.extensionManagementService.getInstalled(ExtensionType.User).then(result => {
384 385 386
			this.hasInstalledExtensionsContextKey.set(result.length > 0);
		});

387
		this._register(this.configurationService.onDidChangeConfiguration(e => {
388 389 390 391 392 393 394
			if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {
				this.secondaryActions = null;
				this.updateTitleArea();
			}
			if (e.affectedKeys.indexOf(ShowRecommendationsOnlyOnDemandKey) > -1) {
				this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
			}
395
		}, this));
396
	}
397

I
isidor 已提交
398
	create(parent: HTMLElement): void {
399 400
		addClass(parent, 'extensions-viewlet');
		this.root = parent;
J
Joao Moreno 已提交
401

402 403 404 405
		const overlay = append(this.root, $('.overlay'));
		const overlayBackgroundColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
		overlay.style.backgroundColor = overlayBackgroundColor;
		hide(overlay);
406

407
		const header = append(this.root, $('.header'));
408
		const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
409
		const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';
J
Joao Moreno 已提交
410

411
		this.searchBox = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, header, {
412
			triggerCharacters: ['@'],
B
Benjamin Pasero 已提交
413
			sortKey: (item: string) => {
414
				if (item.indexOf(':') === -1) { return 'a'; }
S
Sandeep Somavarapu 已提交
415
				else if (/ext:/.test(item) || /id:/.test(item) || /tag:/.test(item)) { return 'b'; }
416 417 418
				else if (/sort:/.test(item)) { return 'c'; }
				else { return 'd'; }
			},
B
Benjamin Pasero 已提交
419
			provideResults: (query: string) => Query.suggestions(query)
420
		}, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue }));
421

422 423 424 425
		if (this.searchBox.getValue()) {
			this.triggerSearch();
		}

426
		this._register(attachSuggestEnabledInputBoxStyler(this.searchBox, this.themeService));
427

428
		this._register(this.searchBox.onInputDidChange(() => {
429
			this.triggerSearch();
S
Sandeep Somavarapu 已提交
430
			this._onSearchChange.fire(this.searchBox!.getValue());
431
		}, this));
432

433
		this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this));
J
Joao Moreno 已提交
434

435
		this._register(this.onDidChangeVisibility(visible => {
436
			if (visible) {
S
Sandeep Somavarapu 已提交
437
				this.searchBox!.focus();
438
			}
439 440
		}));

441 442 443 444 445 446 447 448 449 450 451 452 453 454
		// Register DragAndDrop support
		this._register(new DragAndDropObserver(this.root, {
			onDragEnd: (e: DragEvent) => undefined,
			onDragEnter: (e: DragEvent) => {
				if (this.isSupportedDragElement(e)) {
					show(overlay);
				}
			},
			onDragLeave: (e: DragEvent) => {
				if (this.isSupportedDragElement(e)) {
					hide(overlay);
				}
			},
			onDragOver: (e: DragEvent) => {
455 456
				if (this.isSupportedDragElement(e)) {
					e.dataTransfer!.dropEffect = 'copy';
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
				}
			},
			onDrop: async (e: DragEvent) => {
				if (this.isSupportedDragElement(e)) {
					hide(overlay);

					if (e.dataTransfer && e.dataTransfer.files.length > 0) {
						let vsixPaths: URI[] = [];
						for (let index = 0; index < e.dataTransfer.files.length; index++) {
							const path = e.dataTransfer.files.item(index)!.path;
							if (path.indexOf('.vsix') !== -1) {
								vsixPaths.push(URI.parse(path));
							}
						}

						try {
							// Attempt to install the extension(s)
							await this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL).run(vsixPaths);
						}
						catch (err) {
							this.notificationService.error(err);
						}
					}
				}
481
			}
482 483
		}));

S
Sandeep Somavarapu 已提交
484
		super.create(append(this.root, $('.extensions')));
J
Joao Moreno 已提交
485 486 487
	}

	focus(): void {
S
Sandeep Somavarapu 已提交
488 489 490
		if (this.searchBox) {
			this.searchBox.focus();
		}
J
Joao Moreno 已提交
491 492
	}

S
Sandeep Somavarapu 已提交
493
	layout(dimension: Dimension): void {
S
Sandeep Somavarapu 已提交
494 495 496 497 498 499
		if (this.root) {
			toggleClass(this.root, 'narrow', dimension.width <= 300);
		}
		if (this.searchBox) {
			this.searchBox.layout({ height: 20, width: dimension.width - 34 });
		}
J
Joao Moreno 已提交
500
		super.layout(new Dimension(dimension.width, dimension.height - 41));
J
Joao Moreno 已提交
501 502 503 504
	}

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

J
Joao Moreno 已提交
507
	getActions(): IAction[] {
J
Joao Moreno 已提交
508 509
		if (!this.primaryActions) {
			this.primaryActions = [
S
Sandeep Somavarapu 已提交
510
				this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : '')
J
Joao Moreno 已提交
511
			];
512
		}
J
Joao Moreno 已提交
513
		return this.primaryActions;
J
Joao Moreno 已提交
514 515
	}

516
	getSecondaryActions(): IAction[] {
J
Joao Moreno 已提交
517 518
		if (!this.secondaryActions) {
			this.secondaryActions = [
S
Sandeep Somavarapu 已提交
519
				this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL),
J
Joao Moreno 已提交
520
				this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL),
521
				this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL),
522
				this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL),
S
Sandeep Somavarapu 已提交
523
				this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL),
J
Joao Moreno 已提交
524
				this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL),
J
Joao Moreno 已提交
525 526
				this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL),
				new Separator(),
J
Joao Moreno 已提交
527 528 529
				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 已提交
530
				new Separator(),
J
Joao Moreno 已提交
531
				this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL),
532
				...(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 已提交
533 534 535 536
				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 已提交
537 538 539 540
			];
		}

		return this.secondaryActions;
541 542
	}

S
Sandeep Somavarapu 已提交
543
	search(value: string, refresh: boolean = false): void {
S
Sandeep Somavarapu 已提交
544
		if (this.searchBox) {
S
Sandeep Somavarapu 已提交
545 546 547 548 549
			if (this.searchBox.getValue() !== value) {
				this.searchBox.setValue(value);
			} else if (refresh) {
				this.doSearch();
			}
S
Sandeep Somavarapu 已提交
550
		}
J
Joao Moreno 已提交
551 552
	}

S
Sandeep Somavarapu 已提交
553 554
	private triggerSearch(): void {
		this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err));
555 556 557
	}

	private normalizedQuery(): string {
S
Sandeep Somavarapu 已提交
558
		return this.searchBox ? this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:') : '';
J
Joao Moreno 已提交
559 560
	}

S
SteVen Batten 已提交
561
	saveState(): void {
S
Sandeep Somavarapu 已提交
562
		const value = this.searchBox ? this.searchBox.getValue() : '';
563
		if (ExtensionsListView.isLocalExtensionsQuery(value)) {
564 565 566 567
			this.searchViewletState['query.value'] = value;
		} else {
			this.searchViewletState['query.value'] = '';
		}
568 569 570
		super.saveState();
	}

S
Sandeep Somavarapu 已提交
571
	private doSearch(): Promise<void> {
572
		const value = this.normalizedQuery();
573
		const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value);
574 575 576 577 578
		this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value));
		this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value));
		this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value));
		this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value));
		this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
579
		this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery);
S
Sandeep Somavarapu 已提交
580
		this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery);
581
		this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY);
582
		this.defaultViewsContextKey.set(!value);
583

584
		return this.progress(Promise.all(this.panes.map(view =>
585 586 587
			(<ExtensionsListView>view).show(this.normalizedQuery())
				.then(model => this.alertSearchResult(model.length, view.id))
		))).then(() => undefined);
588 589
	}

590 591
	protected onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] {
		const addedViews = super.onDidAddViewDescriptors(added);
S
Sandeep Somavarapu 已提交
592 593 594 595
		this.progress(Promise.all(addedViews.map(addedView =>
			(<ExtensionsListView>addedView).show(this.normalizedQuery())
				.then(model => this.alertSearchResult(model.length, addedView.id))
		)));
596
		return addedViews;
597 598
	}

S
Sandeep Somavarapu 已提交
599
	private alertSearchResult(count: number, viewId: string): void {
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
		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;
		}
	}

620
	private count(): number {
621
		return this.panes.reduce((count, view) => (<ExtensionsListView>view).count() + count, 0);
J
Joao Moreno 已提交
622 623
	}

624
	private focusListView(): void {
625
		if (this.count() > 0) {
626
			this.panes[0].focus();
627
		}
628 629
	}

630 631 632 633 634
	private onViewletOpen(viewlet: IViewlet): void {
		if (!viewlet || viewlet.getId() === VIEWLET_ID) {
			return;
		}

635 636 637
		if (this.configurationService.getValue<boolean>(CloseExtensionDetailsOnViewChangeKey)) {
			const promises = this.editorGroupService.groups.map(group => {
				const editors = group.editors.filter(input => input instanceof ExtensionsInput);
638

639
				return group.closeEditors(editors);
640
			});
641

642
			Promise.all(promises);
643
		}
644 645
	}

646
	private progress<T>(promise: Promise<T>): Promise<T> {
S
Sandeep Somavarapu 已提交
647
		return this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => promise);
S
Sandeep Somavarapu 已提交
648 649
	}

S
Sandeep Somavarapu 已提交
650
	private onError(err: Error): void {
J
Joao Moreno 已提交
651 652 653 654 655 656
		if (isPromiseCanceledError(err)) {
			return;
		}

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

J
Joao Moreno 已提交
657
		if (/ECONNREFUSED/.test(message)) {
A
Alex Dima 已提交
658
			const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
J
Joao Moreno 已提交
659
				actions: [
S
Sandeep Somavarapu 已提交
660
					new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openGlobalSettings())
J
Joao Moreno 已提交
661 662 663
				]
			});

664
			this.notificationService.error(error);
J
Joao Moreno 已提交
665 666 667
			return;
		}

668
		this.notificationService.error(err);
J
Joao Moreno 已提交
669
	}
670 671 672 673 674 675 676 677 678

	private isSupportedDragElement(e: DragEvent): boolean {
		if (e.dataTransfer) {
			const typesLowerCase = e.dataTransfer.types.map(t => t.toLocaleLowerCase());
			return typesLowerCase.indexOf('files') !== -1;
		}

		return false;
	}
J
Joao Moreno 已提交
679
}
J
Joao Moreno 已提交
680

681
export class StatusUpdater extends Disposable implements IWorkbenchContribution {
J
Joao Moreno 已提交
682

683
	private readonly badgeHandle = this._register(new MutableDisposable());
J
Joao Moreno 已提交
684 685

	constructor(
686
		@IActivityService private readonly activityService: IActivityService,
687
		@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
S
rename  
Sandeep Somavarapu 已提交
688
		@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService
J
Joao Moreno 已提交
689
	) {
690 691
		super();
		this._register(extensionsWorkbenchService.onChange(this.onServiceChange, this));
J
Joao Moreno 已提交
692 693 694
	}

	private onServiceChange(): void {
695
		this.badgeHandle.clear();
696

697
		const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) ? 1 : 0), 0);
698 699
		if (outdated > 0) {
			const badge = new NumberBadge(outdated, n => localize('outdatedExtensions', '{0} Outdated Extensions', n));
S
Sandeep Somavarapu 已提交
700
			this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge, clazz: 'extensions-badge count-badge' });
701
		}
J
Joao Moreno 已提交
702 703
	}
}
704 705 706 707

export class MaliciousExtensionChecker implements IWorkbenchContribution {

	constructor(
708
		@IExtensionManagementService private readonly extensionsManagementService: IExtensionManagementService,
709
		@IHostService private readonly hostService: IHostService,
710 711 712
		@ILogService private readonly logService: ILogService,
		@INotificationService private readonly notificationService: INotificationService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService
713
	) {
714 715 716
		if (!this.environmentService.disableExtensions) {
			this.loopCheckForMaliciousExtensions();
		}
717 718 719 720
	}

	private loopCheckForMaliciousExtensions(): void {
		this.checkForMaliciousExtensions()
721
			.then(() => timeout(1000 * 60 * 5)) // every five minutes
722 723 724
			.then(() => this.loopCheckForMaliciousExtensions());
	}

S
Sandeep Somavarapu 已提交
725
	private checkForMaliciousExtensions(): Promise<void> {
J
Joao Moreno 已提交
726 727 728
		return this.extensionsManagementService.getExtensionsReport().then(report => {
			const maliciousSet = getMaliciousExtensionsSet(report);

729
			return this.extensionsManagementService.getInstalled(ExtensionType.User).then(installed => {
730
				const maliciousExtensions = installed
731
					.filter(e => maliciousSet.has(e.identifier.id));
732 733

				if (maliciousExtensions.length) {
734
					return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
735 736
						this.notificationService.prompt(
							Severity.Warning,
737
							localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", e.identifier.id),
738 739
							[{
								label: localize('reloadNow', "Reload Now"),
740
								run: () => this.hostService.reload()
B
Benjamin Pasero 已提交
741 742
							}],
							{ sticky: true }
743
						);
744 745
					})));
				} else {
S
Sandeep Somavarapu 已提交
746
					return Promise.resolve(undefined);
747
				}
S
Sandeep Somavarapu 已提交
748
			}).then(() => undefined);
749 750
		}, err => this.logService.error(err));
	}
751
}