Skip to content

Commit 87dd7d6

Browse files
committed
Move deltaExtensions related methods up to AbstractExtensionService
1 parent 607ee4a commit 87dd7d6

5 files changed

Lines changed: 269 additions & 242 deletions

File tree

src/vs/workbench/api/common/extHostExtensionService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
506506
const host: IExtensionActivationHost = {
507507
folders: folders.map(folder => folder.uri),
508508
forceUsingSearch: localWithRemote,
509-
exists: (path) => this._hostUtils.exists(path),
509+
exists: (uri) => this._hostUtils.exists(uri.fsPath),
510510
checkExists: (folders, includes, token) => this._mainThreadWorkspaceProxy.$checkExists(folders, includes, token)
511511
};
512512

src/vs/workbench/api/common/shared/workspaceContains.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as path from 'vs/base/common/path';
6+
import * as resources from 'vs/base/common/resources';
77
import { URI, UriComponents } from 'vs/base/common/uri';
88
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
99
import * as errors from 'vs/base/common/errors';
@@ -19,7 +19,7 @@ export interface IExtensionActivationHost {
1919
readonly folders: readonly UriComponents[];
2020
readonly forceUsingSearch: boolean;
2121

22-
exists(path: string): Promise<boolean>;
22+
exists(uri: URI): Promise<boolean>;
2323
checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
2424
}
2525

@@ -69,7 +69,7 @@ export function checkActivateWorkspaceContainsExtension(host: IExtensionActivati
6969
async function _activateIfFileName(host: IExtensionActivationHost, fileName: string, activate: (activationEvent: string) => void): Promise<void> {
7070
// find exact path
7171
for (const uri of host.folders) {
72-
if (await host.exists(path.join(URI.revive(uri).fsPath, fileName))) {
72+
if (await host.exists(resources.joinPath(URI.revive(uri), fileName))) {
7373
// the file was found
7474
activate(`workspaceContains:${fileName}`);
7575
return;

src/vs/workbench/services/extensions/browser/extensionService.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
1919
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
2020
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
2121
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
22-
import { ExtensionIdentifier, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
22+
import { ExtensionIdentifier, IExtensionDescription, ExtensionKind, IExtension } from 'vs/platform/extensions/common/extensions';
2323
import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider';
2424
import { Schemas } from 'vs/base/common/network';
2525
import { DisposableStore } from 'vs/base/common/lifecycle';
2626
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
2727
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
2828
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
29+
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
30+
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
2931

3032
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
3133

@@ -41,6 +43,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
4143
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,
4244
@IFileService fileService: IFileService,
4345
@IProductService productService: IProductService,
46+
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
47+
@IWorkspaceContextService contextService: IWorkspaceContextService,
4448
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
4549
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
4650
@IConfigurationService private readonly _configService: IConfigurationService,
@@ -56,6 +60,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
5660
extensionEnablementService,
5761
fileService,
5862
productService,
63+
extensionManagementService,
64+
contextService,
5965
);
6066

6167
this._runningLocation = new Map<string, ExtensionRunningLocation>();
@@ -74,6 +80,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten
7480
super.dispose();
7581
}
7682

83+
protected async _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null> {
84+
return null;
85+
}
86+
87+
protected async _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
88+
}
89+
7790
private _initFetchFileSystem(): void {
7891
const provider = new FetchFileSystemProvider();
7992
this._disposables.add(this._fileService.registerProvider(Schemas.http, provider));

src/vs/workbench/services/extensions/common/abstractExtensionService.ts

Lines changed: 236 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import * as nls from 'vs/nls';
67
import { isNonEmptyArray } from 'vs/base/common/arrays';
78
import { Barrier } from 'vs/base/common/async';
89
import { Emitter, Event } from 'vs/base/common/event';
@@ -20,11 +21,14 @@ import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensi
2021
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
2122
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
2223
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
23-
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
24+
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension, IExtension } from 'vs/platform/extensions/common/extensions';
2425
import { IFileService } from 'vs/platform/files/common/files';
2526
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
2627
import { IProductService } from 'vs/platform/product/common/productService';
2728
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
29+
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
30+
import { IExtensionActivationHost as IWorkspaceContainsActivationHost, checkGlobFileExists, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
31+
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
2832

2933
const hasOwnProperty = Object.hasOwnProperty;
3034
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
@@ -39,6 +43,13 @@ export function parseScannedExtension(extension: ITranslatedScannedExtension): I
3943
};
4044
}
4145

46+
class DeltaExtensionsQueueItem {
47+
constructor(
48+
public readonly toAdd: IExtension[],
49+
public readonly toRemove: string[]
50+
) { }
51+
}
52+
4253
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
4354

4455
public _serviceBrand: undefined;
@@ -66,6 +77,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
6677
private readonly _proposedApiController: ProposedApiController;
6778
private readonly _isExtensionDevHost: boolean;
6879
protected readonly _isExtensionDevTestFromCli: boolean;
80+
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
81+
private _inHandleDeltaExtensions: boolean;
6982

7083
// --- Members used per extension host process
7184
protected _extensionHostManagers: ExtensionHostManager[];
@@ -80,7 +93,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
8093
@ITelemetryService protected readonly _telemetryService: ITelemetryService,
8194
@IWorkbenchExtensionEnablementService protected readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
8295
@IFileService protected readonly _fileService: IFileService,
83-
@IProductService protected readonly _productService: IProductService
96+
@IProductService protected readonly _productService: IProductService,
97+
@IExtensionManagementService protected readonly _extensionManagementService: IExtensionManagementService,
98+
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
8499
) {
85100
super();
86101

@@ -103,8 +118,225 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
103118
const devOpts = parseExtensionDevOptions(this._environmentService);
104119
this._isExtensionDevHost = devOpts.isExtensionDevHost;
105120
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
121+
122+
this._deltaExtensionsQueue = [];
123+
this._inHandleDeltaExtensions = false;
124+
125+
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
126+
let toAdd: IExtension[] = [];
127+
let toRemove: string[] = [];
128+
for (const extension of extensions) {
129+
if (this._extensionEnablementService.isEnabled(extension)) {
130+
// an extension has been enabled
131+
toAdd.push(extension);
132+
} else {
133+
// an extension has been disabled
134+
toRemove.push(extension.identifier.id);
135+
}
136+
}
137+
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
138+
}));
139+
140+
this._register(this._extensionManagementService.onDidInstallExtension((event) => {
141+
if (event.local) {
142+
if (this._extensionEnablementService.isEnabled(event.local)) {
143+
// an extension has been installed
144+
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([event.local], []));
145+
}
146+
}
147+
}));
148+
149+
this._register(this._extensionManagementService.onDidUninstallExtension((event) => {
150+
if (!event.error) {
151+
// an extension has been uninstalled
152+
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
153+
}
154+
}));
155+
}
156+
157+
//#region deltaExtensions
158+
159+
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
160+
this._deltaExtensionsQueue.push(item);
161+
if (this._inHandleDeltaExtensions) {
162+
// Let the current item finish, the new one will be picked up
163+
return;
164+
}
165+
166+
while (this._deltaExtensionsQueue.length > 0) {
167+
const item = this._deltaExtensionsQueue.shift()!;
168+
try {
169+
this._inHandleDeltaExtensions = true;
170+
await this._deltaExtensions(item.toAdd, item.toRemove);
171+
} finally {
172+
this._inHandleDeltaExtensions = false;
173+
}
174+
}
175+
}
176+
177+
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
178+
if (this._environmentService.configuration.remoteAuthority) {
179+
return;
180+
}
181+
182+
let toAdd: IExtensionDescription[] = [];
183+
for (let i = 0, len = _toAdd.length; i < len; i++) {
184+
const extension = _toAdd[i];
185+
186+
if (!this._canAddExtension(extension)) {
187+
continue;
188+
}
189+
190+
const extensionDescription = await this._scanSingleExtension(extension);
191+
if (!extensionDescription) {
192+
// could not scan extension...
193+
continue;
194+
}
195+
196+
toAdd.push(extensionDescription);
197+
}
198+
199+
let toRemove: IExtensionDescription[] = [];
200+
for (let i = 0, len = _toRemove.length; i < len; i++) {
201+
const extensionId = _toRemove[i];
202+
const extensionDescription = this._registry.getExtensionDescription(extensionId);
203+
if (!extensionDescription) {
204+
// ignore disabling/uninstalling an extension which is not running
205+
continue;
206+
}
207+
208+
if (!this.canRemoveExtension(extensionDescription)) {
209+
// uses non-dynamic extension point or is activated
210+
continue;
211+
}
212+
213+
toRemove.push(extensionDescription);
214+
}
215+
216+
if (toAdd.length === 0 && toRemove.length === 0) {
217+
return;
218+
}
219+
220+
// Update the local registry
221+
const result = this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
222+
this._onDidChangeExtensions.fire(undefined);
223+
224+
toRemove = toRemove.concat(result.removedDueToLooping);
225+
if (result.removedDueToLooping.length > 0) {
226+
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
227+
}
228+
229+
// enable or disable proposed API per extension
230+
this._checkEnableProposedApi(toAdd);
231+
232+
// Update extension points
233+
this._doHandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
234+
235+
// Update the extension host
236+
this._updateExtensionsOnExtHosts(toAdd, toRemove.map(e => e.identifier));
237+
238+
for (let i = 0; i < toAdd.length; i++) {
239+
this._activateAddedExtensionIfNeeded(toAdd[i]);
240+
}
241+
}
242+
243+
public canAddExtension(extensionDescription: IExtensionDescription): boolean {
244+
return this._canAddExtension(toExtension(extensionDescription));
245+
}
246+
247+
protected _canAddExtension(extension: IExtension): boolean {
248+
const extensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
249+
if (extensionDescription) {
250+
// this extension is already running (most likely at a different version)
251+
return false;
252+
}
253+
254+
// Check if extension is renamed
255+
if (extension.identifier.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.identifier.uuid)) {
256+
return false;
257+
}
258+
259+
return true;
260+
}
261+
262+
public canRemoveExtension(extension: IExtensionDescription): boolean {
263+
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
264+
if (!extensionDescription) {
265+
// ignore removing an extension which is not running
266+
return false;
267+
}
268+
269+
if (this._extensionHostActiveExtensions.has(ExtensionIdentifier.toKey(extensionDescription.identifier))) {
270+
// Extension is running, cannot remove it safely
271+
return false;
272+
}
273+
274+
return true;
275+
}
276+
277+
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
278+
let shouldActivate = false;
279+
let shouldActivateReason: string | null = null;
280+
let hasWorkspaceContains = false;
281+
if (Array.isArray(extensionDescription.activationEvents)) {
282+
for (let activationEvent of extensionDescription.activationEvents) {
283+
// TODO@joao: there's no easy way to contribute this
284+
if (activationEvent === 'onUri') {
285+
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
286+
}
287+
288+
if (this._allRequestedActivateEvents.has(activationEvent)) {
289+
// This activation event was fired before the extension was added
290+
shouldActivate = true;
291+
shouldActivateReason = activationEvent;
292+
break;
293+
}
294+
295+
if (activationEvent === '*') {
296+
shouldActivate = true;
297+
shouldActivateReason = activationEvent;
298+
break;
299+
}
300+
301+
if (/^workspaceContains/.test(activationEvent)) {
302+
hasWorkspaceContains = true;
303+
}
304+
305+
if (activationEvent === 'onStartupFinished') {
306+
shouldActivate = true;
307+
shouldActivateReason = activationEvent;
308+
break;
309+
}
310+
}
311+
}
312+
313+
if (shouldActivate) {
314+
await Promise.all(
315+
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! }))
316+
).then(() => { });
317+
} else if (hasWorkspaceContains) {
318+
const workspace = await this._contextService.getCompleteWorkspace();
319+
const forceUsingSearch = !!this._environmentService.configuration.remoteAuthority;
320+
const host: IWorkspaceContainsActivationHost = {
321+
folders: workspace.folders.map(folder => folder.uri),
322+
forceUsingSearch: forceUsingSearch,
323+
exists: (uri) => this._fileService.exists(uri),
324+
checkExists: (folders, includes, token) => this._instantiationService.invokeFunction((accessor) => checkGlobFileExists(accessor, folders, includes, token))
325+
};
326+
327+
const result = await checkActivateWorkspaceContainsExtension(host, extensionDescription);
328+
if (!result) {
329+
return;
330+
}
331+
332+
await Promise.all(
333+
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: result.activationEvent }))
334+
).then(() => { });
335+
}
106336
}
107337

338+
//#endregion
339+
108340
protected async _initialize(): Promise<void> {
109341
perf.mark('willLoadExtensions');
110342
this._startExtensionHosts(true, []);
@@ -169,14 +401,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
169401

170402
//#region IExtensionService
171403

172-
public canAddExtension(extension: IExtensionDescription): boolean {
173-
return false;
174-
}
175-
176-
public canRemoveExtension(extension: IExtensionDescription): boolean {
177-
return false;
178-
}
179-
180404
public restartExtensionHost(): void {
181405
this._stopExtensionHosts();
182406
this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys()));
@@ -463,6 +687,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
463687

464688
protected abstract _createExtensionHosts(isInitialStart: boolean): IExtensionHost[];
465689
protected abstract _scanAndHandleExtensions(): Promise<void>;
690+
protected abstract _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null>;
691+
protected abstract _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void>;
466692
public abstract _onExtensionHostExit(code: number): void;
467693
}
468694

0 commit comments

Comments
 (0)