mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-21 23:59:34 +01:00
Move deltaExtensions related methods up to AbstractExtensionService
This commit is contained in:
@@ -506,7 +506,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
const host: IExtensionActivationHost = {
|
||||
folders: folders.map(folder => folder.uri),
|
||||
forceUsingSearch: localWithRemote,
|
||||
exists: (path) => this._hostUtils.exists(path),
|
||||
exists: (uri) => this._hostUtils.exists(uri.fsPath),
|
||||
checkExists: (folders, includes, token) => this._mainThreadWorkspaceProxy.$checkExists(folders, includes, token)
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
@@ -19,7 +19,7 @@ export interface IExtensionActivationHost {
|
||||
readonly folders: readonly UriComponents[];
|
||||
readonly forceUsingSearch: boolean;
|
||||
|
||||
exists(path: string): Promise<boolean>;
|
||||
exists(uri: URI): Promise<boolean>;
|
||||
checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export function checkActivateWorkspaceContainsExtension(host: IExtensionActivati
|
||||
async function _activateIfFileName(host: IExtensionActivationHost, fileName: string, activate: (activationEvent: string) => void): Promise<void> {
|
||||
// find exact path
|
||||
for (const uri of host.folders) {
|
||||
if (await host.exists(path.join(URI.revive(uri).fsPath, fileName))) {
|
||||
if (await host.exists(resources.joinPath(URI.revive(uri), fileName))) {
|
||||
// the file was found
|
||||
activate(`workspaceContains:${fileName}`);
|
||||
return;
|
||||
|
||||
@@ -19,13 +19,15 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
|
||||
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
|
||||
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ExtensionIdentifier, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription, ExtensionKind, IExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
|
||||
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
|
||||
|
||||
@@ -41,6 +43,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IProductService productService: IProductService,
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
@@ -56,6 +60,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
extensionEnablementService,
|
||||
fileService,
|
||||
productService,
|
||||
extensionManagementService,
|
||||
contextService,
|
||||
);
|
||||
|
||||
this._runningLocation = new Map<string, ExtensionRunningLocation>();
|
||||
@@ -74,6 +80,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected async _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
|
||||
}
|
||||
|
||||
private _initFetchFileSystem(): void {
|
||||
const provider = new FetchFileSystemProvider();
|
||||
this._disposables.add(this._fileService.registerProvider(Schemas.http, provider));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
@@ -20,11 +21,14 @@ import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensi
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
|
||||
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
|
||||
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension, IExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionActivationHost as IWorkspaceContainsActivationHost, checkGlobFileExists, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
|
||||
@@ -39,6 +43,13 @@ export function parseScannedExtension(extension: ITranslatedScannedExtension): I
|
||||
};
|
||||
}
|
||||
|
||||
class DeltaExtensionsQueueItem {
|
||||
constructor(
|
||||
public readonly toAdd: IExtension[],
|
||||
public readonly toRemove: string[]
|
||||
) { }
|
||||
}
|
||||
|
||||
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
public _serviceBrand: undefined;
|
||||
@@ -66,6 +77,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
private readonly _proposedApiController: ProposedApiController;
|
||||
private readonly _isExtensionDevHost: boolean;
|
||||
protected readonly _isExtensionDevTestFromCli: boolean;
|
||||
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
|
||||
private _inHandleDeltaExtensions: boolean;
|
||||
|
||||
// --- Members used per extension host process
|
||||
protected _extensionHostManagers: ExtensionHostManager[];
|
||||
@@ -80,7 +93,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
@ITelemetryService protected readonly _telemetryService: ITelemetryService,
|
||||
@IWorkbenchExtensionEnablementService protected readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IFileService protected readonly _fileService: IFileService,
|
||||
@IProductService protected readonly _productService: IProductService
|
||||
@IProductService protected readonly _productService: IProductService,
|
||||
@IExtensionManagementService protected readonly _extensionManagementService: IExtensionManagementService,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -103,8 +118,225 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
const devOpts = parseExtensionDevOptions(this._environmentService);
|
||||
this._isExtensionDevHost = devOpts.isExtensionDevHost;
|
||||
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
|
||||
|
||||
this._deltaExtensionsQueue = [];
|
||||
this._inHandleDeltaExtensions = false;
|
||||
|
||||
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
|
||||
let toAdd: IExtension[] = [];
|
||||
let toRemove: string[] = [];
|
||||
for (const extension of extensions) {
|
||||
if (this._extensionEnablementService.isEnabled(extension)) {
|
||||
// an extension has been enabled
|
||||
toAdd.push(extension);
|
||||
} else {
|
||||
// an extension has been disabled
|
||||
toRemove.push(extension.identifier.id);
|
||||
}
|
||||
}
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
|
||||
}));
|
||||
|
||||
this._register(this._extensionManagementService.onDidInstallExtension((event) => {
|
||||
if (event.local) {
|
||||
if (this._extensionEnablementService.isEnabled(event.local)) {
|
||||
// an extension has been installed
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([event.local], []));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._extensionManagementService.onDidUninstallExtension((event) => {
|
||||
if (!event.error) {
|
||||
// an extension has been uninstalled
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
//#region deltaExtensions
|
||||
|
||||
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
|
||||
this._deltaExtensionsQueue.push(item);
|
||||
if (this._inHandleDeltaExtensions) {
|
||||
// Let the current item finish, the new one will be picked up
|
||||
return;
|
||||
}
|
||||
|
||||
while (this._deltaExtensionsQueue.length > 0) {
|
||||
const item = this._deltaExtensionsQueue.shift()!;
|
||||
try {
|
||||
this._inHandleDeltaExtensions = true;
|
||||
await this._deltaExtensions(item.toAdd, item.toRemove);
|
||||
} finally {
|
||||
this._inHandleDeltaExtensions = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
|
||||
if (this._environmentService.configuration.remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
|
||||
let toAdd: IExtensionDescription[] = [];
|
||||
for (let i = 0, len = _toAdd.length; i < len; i++) {
|
||||
const extension = _toAdd[i];
|
||||
|
||||
if (!this._canAddExtension(extension)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const extensionDescription = await this._scanSingleExtension(extension);
|
||||
if (!extensionDescription) {
|
||||
// could not scan extension...
|
||||
continue;
|
||||
}
|
||||
|
||||
toAdd.push(extensionDescription);
|
||||
}
|
||||
|
||||
let toRemove: IExtensionDescription[] = [];
|
||||
for (let i = 0, len = _toRemove.length; i < len; i++) {
|
||||
const extensionId = _toRemove[i];
|
||||
const extensionDescription = this._registry.getExtensionDescription(extensionId);
|
||||
if (!extensionDescription) {
|
||||
// ignore disabling/uninstalling an extension which is not running
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.canRemoveExtension(extensionDescription)) {
|
||||
// uses non-dynamic extension point or is activated
|
||||
continue;
|
||||
}
|
||||
|
||||
toRemove.push(extensionDescription);
|
||||
}
|
||||
|
||||
if (toAdd.length === 0 && toRemove.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the local registry
|
||||
const result = this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
|
||||
this._onDidChangeExtensions.fire(undefined);
|
||||
|
||||
toRemove = toRemove.concat(result.removedDueToLooping);
|
||||
if (result.removedDueToLooping.length > 0) {
|
||||
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(', ')));
|
||||
}
|
||||
|
||||
// enable or disable proposed API per extension
|
||||
this._checkEnableProposedApi(toAdd);
|
||||
|
||||
// Update extension points
|
||||
this._doHandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
|
||||
|
||||
// Update the extension host
|
||||
this._updateExtensionsOnExtHosts(toAdd, toRemove.map(e => e.identifier));
|
||||
|
||||
for (let i = 0; i < toAdd.length; i++) {
|
||||
this._activateAddedExtensionIfNeeded(toAdd[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public canAddExtension(extensionDescription: IExtensionDescription): boolean {
|
||||
return this._canAddExtension(toExtension(extensionDescription));
|
||||
}
|
||||
|
||||
protected _canAddExtension(extension: IExtension): boolean {
|
||||
const extensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
|
||||
if (extensionDescription) {
|
||||
// this extension is already running (most likely at a different version)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if extension is renamed
|
||||
if (extension.identifier.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.identifier.uuid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public canRemoveExtension(extension: IExtensionDescription): boolean {
|
||||
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
|
||||
if (!extensionDescription) {
|
||||
// ignore removing an extension which is not running
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._extensionHostActiveExtensions.has(ExtensionIdentifier.toKey(extensionDescription.identifier))) {
|
||||
// Extension is running, cannot remove it safely
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
|
||||
let shouldActivate = false;
|
||||
let shouldActivateReason: string | null = null;
|
||||
let hasWorkspaceContains = false;
|
||||
if (Array.isArray(extensionDescription.activationEvents)) {
|
||||
for (let activationEvent of extensionDescription.activationEvents) {
|
||||
// TODO@joao: there's no easy way to contribute this
|
||||
if (activationEvent === 'onUri') {
|
||||
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
|
||||
}
|
||||
|
||||
if (this._allRequestedActivateEvents.has(activationEvent)) {
|
||||
// This activation event was fired before the extension was added
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
|
||||
if (activationEvent === '*') {
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
|
||||
if (/^workspaceContains/.test(activationEvent)) {
|
||||
hasWorkspaceContains = true;
|
||||
}
|
||||
|
||||
if (activationEvent === 'onStartupFinished') {
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldActivate) {
|
||||
await Promise.all(
|
||||
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! }))
|
||||
).then(() => { });
|
||||
} else if (hasWorkspaceContains) {
|
||||
const workspace = await this._contextService.getCompleteWorkspace();
|
||||
const forceUsingSearch = !!this._environmentService.configuration.remoteAuthority;
|
||||
const host: IWorkspaceContainsActivationHost = {
|
||||
folders: workspace.folders.map(folder => folder.uri),
|
||||
forceUsingSearch: forceUsingSearch,
|
||||
exists: (uri) => this._fileService.exists(uri),
|
||||
checkExists: (folders, includes, token) => this._instantiationService.invokeFunction((accessor) => checkGlobFileExists(accessor, folders, includes, token))
|
||||
};
|
||||
|
||||
const result = await checkActivateWorkspaceContainsExtension(host, extensionDescription);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: result.activationEvent }))
|
||||
).then(() => { });
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
protected async _initialize(): Promise<void> {
|
||||
perf.mark('willLoadExtensions');
|
||||
this._startExtensionHosts(true, []);
|
||||
@@ -169,14 +401,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
|
||||
//#region IExtensionService
|
||||
|
||||
public canAddExtension(extension: IExtensionDescription): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public canRemoveExtension(extension: IExtensionDescription): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public restartExtensionHost(): void {
|
||||
this._stopExtensionHosts();
|
||||
this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys()));
|
||||
@@ -463,6 +687,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
|
||||
protected abstract _createExtensionHosts(isInitialStart: boolean): IExtensionHost[];
|
||||
protected abstract _scanAndHandleExtensions(): Promise<void>;
|
||||
protected abstract _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null>;
|
||||
protected abstract _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void>;
|
||||
public abstract _onExtensionHostExit(code: number): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IExtensionService, toExtension, ExtensionHostKind, IExtensionHost, webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
|
||||
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
@@ -37,18 +36,10 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
|
||||
import { IExtensionActivationHost as IWorkspaceContainsActivationHost, checkGlobFileExists, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
|
||||
class DeltaExtensionsQueueItem {
|
||||
constructor(
|
||||
public readonly toAdd: IExtension[],
|
||||
public readonly toRemove: string[]
|
||||
) { }
|
||||
}
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
|
||||
|
||||
@@ -56,7 +47,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
private readonly _remoteInitData: Map<string, IRemoteExtensionHostInitData>;
|
||||
private _runningLocation: Map<string, ExtensionRunningLocation>;
|
||||
private readonly _extensionScanner: CachedExtensionScanner;
|
||||
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@@ -66,7 +56,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IProductService productService: IProductService,
|
||||
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@@ -76,7 +67,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
@IHostService private readonly _hostService: IHostService,
|
||||
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
|
||||
@IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
) {
|
||||
super(
|
||||
@@ -86,7 +76,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
telemetryService,
|
||||
extensionEnablementService,
|
||||
fileService,
|
||||
productService
|
||||
productService,
|
||||
extensionManagementService,
|
||||
contextService,
|
||||
);
|
||||
|
||||
this._enableLocalWebWorker = this._configurationService.getValue<boolean>(webWorkerExtHostConfig);
|
||||
@@ -95,38 +87,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
this._runningLocation = new Map<string, ExtensionRunningLocation>();
|
||||
|
||||
this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner);
|
||||
this._deltaExtensionsQueue = [];
|
||||
|
||||
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
|
||||
let toAdd: IExtension[] = [];
|
||||
let toRemove: string[] = [];
|
||||
for (const extension of extensions) {
|
||||
if (this._extensionEnablementService.isEnabled(extension)) {
|
||||
// an extension has been enabled
|
||||
toAdd.push(extension);
|
||||
} else {
|
||||
// an extension has been disabled
|
||||
toRemove.push(extension.identifier.id);
|
||||
}
|
||||
}
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
|
||||
}));
|
||||
|
||||
this._register(this._extensionManagementService.onDidInstallExtension((event) => {
|
||||
if (event.local) {
|
||||
if (this._extensionEnablementService.isEnabled(event.local)) {
|
||||
// an extension has been installed
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([event.local], []));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._extensionManagementService.onDidUninstallExtension((event) => {
|
||||
if (!event.error) {
|
||||
// an extension has been uninstalled
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
|
||||
}
|
||||
}));
|
||||
|
||||
// delay extension host creation and extension scanning
|
||||
// until the workbench is running. we cannot defer the
|
||||
@@ -163,101 +123,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
return null;
|
||||
}
|
||||
|
||||
//#region deltaExtensions
|
||||
|
||||
private _inHandleDeltaExtensions = false;
|
||||
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
|
||||
this._deltaExtensionsQueue.push(item);
|
||||
if (this._inHandleDeltaExtensions) {
|
||||
// Let the current item finish, the new one will be picked up
|
||||
return;
|
||||
}
|
||||
|
||||
while (this._deltaExtensionsQueue.length > 0) {
|
||||
const item = this._deltaExtensionsQueue.shift()!;
|
||||
try {
|
||||
this._inHandleDeltaExtensions = true;
|
||||
await this._deltaExtensions(item.toAdd, item.toRemove);
|
||||
} finally {
|
||||
this._inHandleDeltaExtensions = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
|
||||
if (this._environmentService.configuration.remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
|
||||
let toAdd: IExtensionDescription[] = [];
|
||||
for (let i = 0, len = _toAdd.length; i < len; i++) {
|
||||
const extension = _toAdd[i];
|
||||
|
||||
if (!this._canAddExtension(extension)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const extensionDescription = await this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System, this.createLogger());
|
||||
if (!extensionDescription) {
|
||||
// could not scan extension...
|
||||
continue;
|
||||
}
|
||||
|
||||
toAdd.push(extensionDescription);
|
||||
}
|
||||
|
||||
let toRemove: IExtensionDescription[] = [];
|
||||
for (let i = 0, len = _toRemove.length; i < len; i++) {
|
||||
const extensionId = _toRemove[i];
|
||||
const extensionDescription = this._registry.getExtensionDescription(extensionId);
|
||||
if (!extensionDescription) {
|
||||
// ignore disabling/uninstalling an extension which is not running
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this._canRemoveExtension(extensionDescription)) {
|
||||
// uses non-dynamic extension point or is activated
|
||||
continue;
|
||||
}
|
||||
|
||||
toRemove.push(extensionDescription);
|
||||
}
|
||||
|
||||
if (toAdd.length === 0 && toRemove.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the local registry
|
||||
const result = this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
|
||||
this._onDidChangeExtensions.fire(undefined);
|
||||
|
||||
toRemove = toRemove.concat(result.removedDueToLooping);
|
||||
if (result.removedDueToLooping.length > 0) {
|
||||
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(', ')));
|
||||
}
|
||||
|
||||
// enable or disable proposed API per extension
|
||||
this._checkEnableProposedApi(toAdd);
|
||||
|
||||
// Update extension points
|
||||
this._doHandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
|
||||
|
||||
// Update the extension host
|
||||
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess);
|
||||
if (localProcessExtensionHost) {
|
||||
await localProcessExtensionHost.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
|
||||
}
|
||||
|
||||
for (let i = 0; i < toAdd.length; i++) {
|
||||
this._activateAddedExtensionIfNeeded(toAdd[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public canAddExtension(extensionDescription: IExtensionDescription): boolean {
|
||||
return this._canAddExtension(toExtension(extensionDescription));
|
||||
}
|
||||
|
||||
private _canAddExtension(extension: IExtension): boolean {
|
||||
protected _canAddExtension(extension: IExtension): boolean {
|
||||
if (this._environmentService.configuration.remoteAuthority) {
|
||||
return false;
|
||||
}
|
||||
@@ -266,18 +132,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
return false;
|
||||
}
|
||||
|
||||
const extensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
|
||||
if (extensionDescription) {
|
||||
// this extension is already running (most likely at a different version)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if extension is renamed
|
||||
if (extension.identifier.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.identifier.uuid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return super._canAddExtension(extension);
|
||||
}
|
||||
|
||||
public canRemoveExtension(extension: IExtensionDescription): boolean {
|
||||
@@ -289,88 +144,21 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
return false;
|
||||
}
|
||||
|
||||
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
|
||||
if (!extensionDescription) {
|
||||
// ignore removing an extension which is not running
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._canRemoveExtension(extensionDescription);
|
||||
return super.canRemoveExtension(extension);
|
||||
}
|
||||
|
||||
private _canRemoveExtension(extension: IExtensionDescription): boolean {
|
||||
if (this._extensionHostActiveExtensions.has(ExtensionIdentifier.toKey(extension.identifier))) {
|
||||
// Extension is running, cannot remove it safely
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
protected _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null> {
|
||||
return this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System, this.createLogger());
|
||||
}
|
||||
|
||||
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
|
||||
|
||||
let shouldActivate = false;
|
||||
let shouldActivateReason: string | null = null;
|
||||
let hasWorkspaceContains = false;
|
||||
if (Array.isArray(extensionDescription.activationEvents)) {
|
||||
for (let activationEvent of extensionDescription.activationEvents) {
|
||||
// TODO@joao: there's no easy way to contribute this
|
||||
if (activationEvent === 'onUri') {
|
||||
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
|
||||
}
|
||||
|
||||
if (this._allRequestedActivateEvents.has(activationEvent)) {
|
||||
// This activation event was fired before the extension was added
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
|
||||
if (activationEvent === '*') {
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
|
||||
if (/^workspaceContains/.test(activationEvent)) {
|
||||
hasWorkspaceContains = true;
|
||||
}
|
||||
|
||||
if (activationEvent === 'onStartupFinished') {
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldActivate) {
|
||||
await Promise.all(
|
||||
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! }))
|
||||
).then(() => { });
|
||||
} else if (hasWorkspaceContains) {
|
||||
const workspace = await this._contextService.getCompleteWorkspace();
|
||||
const forceUsingSearch = !!this._environmentService.configuration.remoteAuthority;
|
||||
const host: IWorkspaceContainsActivationHost = {
|
||||
folders: workspace.folders.map(folder => folder.uri),
|
||||
forceUsingSearch: forceUsingSearch,
|
||||
exists: (path) => exists(path),
|
||||
checkExists: (folders, includes, token) => this._instantiationService.invokeFunction((accessor) => checkGlobFileExists(accessor, folders, includes, token))
|
||||
};
|
||||
|
||||
const result = await checkActivateWorkspaceContainsExtension(host, extensionDescription);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: result.activationEvent }))
|
||||
).then(() => { });
|
||||
protected async _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
|
||||
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess);
|
||||
if (localProcessExtensionHost) {
|
||||
await localProcessExtensionHost.deltaExtensions(toAdd, toRemove);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
private async _scanAllLocalExtensions(): Promise<IExtensionDescription[]> {
|
||||
return flatten(await Promise.all([
|
||||
this._extensionScanner.scannedExtensions,
|
||||
|
||||
Reference in New Issue
Block a user