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' ;
67import { isNonEmptyArray } from 'vs/base/common/arrays' ;
78import { Barrier } from 'vs/base/common/async' ;
89import { Emitter , Event } from 'vs/base/common/event' ;
@@ -20,11 +21,14 @@ import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensi
2021import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry' ;
2122import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol' ;
2223import { 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' ;
2425import { IFileService } from 'vs/platform/files/common/files' ;
2526import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions' ;
2627import { IProductService } from 'vs/platform/product/common/productService' ;
2728import { 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
2933const hasOwnProperty = Object . hasOwnProperty ;
3034const 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+
4253export 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 ( / ^ w o r k s p a c e C o n t a i n s / . 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