Break up ExtensionService into multiple files

This commit is contained in:
Alex Dima 2018-11-25 12:53:08 +01:00
parent 8f688a4ccf
commit 0f3eefc626
3 changed files with 629 additions and 582 deletions

View file

@ -0,0 +1,372 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as os from 'os';
import * as path from 'path';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import * as errors from 'vs/base/common/errors';
import { Schemas } from 'vs/base/common/network';
import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { fsPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, ILog, IRelaxedExtensionDescription, Translations } from 'vs/workbench/services/extensions/node/extensionPoints';
interface IExtensionCacheData {
input: ExtensionScannerInput;
result: IExtensionDescription[];
}
let _SystemExtensionsRoot: string | null = null;
function getSystemExtensionsRoot(): string {
if (!_SystemExtensionsRoot) {
_SystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'extensions'));
}
return _SystemExtensionsRoot;
}
let _ExtraDevSystemExtensionsRoot: string | null = null;
function getExtraDevSystemExtensionsRoot(): string {
if (!_ExtraDevSystemExtensionsRoot) {
_ExtraDevSystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', '.build', 'builtInExtensions'));
}
return _ExtraDevSystemExtensionsRoot;
}
export class CachedExtensionScanner {
public readonly scannedExtensions: Promise<IExtensionDescription[]>;
private _scannedExtensionsResolve: (result: IExtensionDescription[]) => void;
private _scannedExtensionsReject: (err: any) => void;
constructor(
@INotificationService private readonly _notificationService: INotificationService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IWindowService private readonly _windowService: IWindowService,
) {
this.scannedExtensions = new Promise<IExtensionDescription[]>((resolve, reject) => {
this._scannedExtensionsResolve = resolve;
this._scannedExtensionsReject = reject;
});
}
public startScanningExtensions(log: ILog): void {
CachedExtensionScanner._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log)
.then(({ system, user, development }) => {
let result: { [extensionId: string]: IExtensionDescription; } = {};
system.forEach((systemExtension) => {
result[systemExtension.id] = systemExtension;
});
user.forEach((userExtension) => {
if (result.hasOwnProperty(userExtension.id)) {
log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionLocation.fsPath, userExtension.extensionLocation.fsPath));
}
result[userExtension.id] = userExtension;
});
development.forEach(developedExtension => {
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath));
if (result.hasOwnProperty(developedExtension.id)) {
log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionLocation.fsPath, developedExtension.extensionLocation.fsPath));
}
result[developedExtension.id] = developedExtension;
});
return Object.keys(result).map(name => result[name]);
})
.then(this._scannedExtensionsResolve, this._scannedExtensionsReject);
}
private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise<void> {
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, cacheKey);
const expected = JSON.parse(JSON.stringify(await ExtensionScanner.scanExtensions(input, new NullLogger())));
const cacheContents = await this._readExtensionCache(environmentService, cacheKey);
if (!cacheContents) {
// Cache has been deleted by someone else, which is perfectly fine...
return;
}
const actual = cacheContents.result;
if (objects.equals(expected, actual)) {
// Cache is valid and running with it is perfectly fine...
return;
}
try {
await pfs.del(cacheFile);
} catch (err) {
errors.onUnexpectedError(err);
console.error(err);
}
notificationService.prompt(
Severity.Error,
nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."),
[{
label: nls.localize('reloadWindow', "Reload Window"),
run: () => windowService.reloadWindow()
}]
);
}
private static async _readExtensionCache(environmentService: IEnvironmentService, cacheKey: string): Promise<IExtensionCacheData> {
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, cacheKey);
try {
const cacheRawContents = await pfs.readFile(cacheFile, 'utf8');
return JSON.parse(cacheRawContents);
} catch (err) {
// That's ok...
}
return null;
}
private static async _writeExtensionCache(environmentService: IEnvironmentService, cacheKey: string, cacheContents: IExtensionCacheData): Promise<void> {
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, cacheKey);
try {
await pfs.mkdirp(cacheFolder);
} catch (err) {
// That's ok...
}
try {
await pfs.writeFile(cacheFile, JSON.stringify(cacheContents));
} catch (err) {
// That's ok...
}
}
private static async _scanExtensionsWithCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise<IExtensionDescription[]> {
if (input.devMode) {
// Do not cache when running out of sources...
return ExtensionScanner.scanExtensions(input, log);
}
try {
const folderStat = await pfs.stat(input.absoluteFolderPath);
input.mtime = folderStat.mtime.getTime();
} catch (err) {
// That's ok...
}
const cacheContents = await this._readExtensionCache(environmentService, cacheKey);
if (cacheContents && cacheContents.input && ExtensionScannerInput.equals(cacheContents.input, input)) {
// Validate the cache asynchronously after 5s
setTimeout(async () => {
try {
await this._validateExtensionsCache(windowService, notificationService, environmentService, cacheKey, input);
} catch (err) {
errors.onUnexpectedError(err);
}
}, 5000);
return cacheContents.result.map((extensionDescription) => {
// revive URI object
(<IRelaxedExtensionDescription>extensionDescription).extensionLocation = URI.revive(extensionDescription.extensionLocation);
return extensionDescription;
});
}
const counterLogger = new CounterLogger(log);
const result = await ExtensionScanner.scanExtensions(input, counterLogger);
if (counterLogger.errorCnt === 0) {
// Nothing bad happened => cache the result
const cacheContents: IExtensionCacheData = {
input: input,
result: result
};
await this._writeExtensionCache(environmentService, cacheKey, cacheContents);
}
return result;
}
private static _scanInstalledExtensions(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, extensionEnablementService: IExtensionEnablementService, log: ILog): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
const translationConfig: Promise<Translations> = platform.translationsConfigFile
? pfs.readFile(platform.translationsConfigFile, 'utf8').then((content) => {
try {
return JSON.parse(content) as Translations;
} catch (err) {
return Object.create(null);
}
}, (err) => {
return Object.create(null);
})
: Promise.resolve(Object.create(null));
return translationConfig.then((translations) => {
const version = pkg.version;
const commit = product.commit;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.locale;
const builtinExtensions = this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
BUILTIN_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
log
);
let finalBuiltinExtensions: Promise<IExtensionDescription[]> = builtinExtensions;
if (devMode) {
const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json'));
const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8')
.then<IBuiltInExtension[]>(raw => JSON.parse(raw));
const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
const controlFile = pfs.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
finalBuiltinExtensions = ExtensionScanner.mergeBuiltinExtensions(builtinExtensions, extraBuiltinExtensions);
}
const userExtensions = (
extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath
? Promise.resolve([])
: this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
USER_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
log
)
);
// Always load developed extensions while extensions development
let developedExtensions = Promise.resolve([]);
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) {
developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, fsPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log
);
}
return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => {
const system = extensionDescriptions[0];
const user = extensionDescriptions[1];
const development = extensionDescriptions[2];
return { system, user, development };
}).then(null, err => {
log.error('', err);
return { system: [], user: [], development: [] };
});
});
}
}
interface IBuiltInExtension {
name: string;
version: string;
repo: string;
}
interface IBuiltInExtensionControl {
[name: string]: 'marketplace' | 'disabled' | string;
}
class ExtraBuiltInExtensionResolver implements IExtensionResolver {
constructor(private builtInExtensions: IBuiltInExtension[], private control: IBuiltInExtensionControl) { }
resolveExtensions(): Promise<IExtensionReference[]> {
const result: IExtensionReference[] = [];
for (const ext of this.builtInExtensions) {
const controlState = this.control[ext.name] || 'marketplace';
switch (controlState) {
case 'disabled':
break;
case 'marketplace':
result.push({ name: ext.name, path: path.join(getExtraDevSystemExtensionsRoot(), ext.name) });
break;
default:
result.push({ name: ext.name, path: controlState });
break;
}
}
return Promise.resolve(result);
}
}
class CounterLogger implements ILog {
public errorCnt = 0;
public warnCnt = 0;
public infoCnt = 0;
constructor(private readonly _actual: ILog) {
}
public error(source: string, message: string): void {
this._actual.error(source, message);
}
public warn(source: string, message: string): void {
this._actual.warn(source, message);
}
public info(source: string, message: string): void {
this._actual.info(source, message);
}
}
class NullLogger implements ILog {
public error(source: string, message: string): void {
}
public warn(source: string, message: string): void {
}
public info(source: string, message: string): void {
}
}
export class Logger implements ILog {
private readonly _messageHandler: (severity: Severity, source: string, message: string) => void;
constructor(
messageHandler: (severity: Severity, source: string, message: string) => void
) {
this._messageHandler = messageHandler;
}
public error(source: string, message: string): void {
this._messageHandler(Severity.Error, source, message);
}
public warn(source: string, message: string): void {
this._messageHandler(Severity.Warning, source, message);
}
public info(source: string, message: string): void {
this._messageHandler(Severity.Info, source, message);
}
}

View file

@ -0,0 +1,230 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import * as strings from 'vs/base/common/strings';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
import { ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
const LOG_USE_COLORS = true;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
export class ExtensionHostProcessManager extends Disposable {
public readonly onDidCrash: Event<[number, string]>;
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
/**
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
*/
private readonly _extensionHostProcessFinishedActivateEvents: { [activationEvent: string]: boolean; };
private _extensionHostProcessRPCProtocol: RPCProtocol;
private readonly _extensionHostProcessCustomers: IDisposable[];
private readonly _extensionHostProcessWorker: IExtensionHostStarter;
/**
* winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object.
*/
private _extensionHostProcessProxy: Thenable<{ value: ExtHostExtensionServiceShape; }>;
constructor(
extensionHostProcessWorker: IExtensionHostStarter,
private readonly _remoteAuthority: string,
initialActivationEvents: string[],
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
) {
super();
this._extensionHostProcessFinishedActivateEvents = Object.create(null);
this._extensionHostProcessRPCProtocol = null;
this._extensionHostProcessCustomers = [];
this._extensionHostProcessWorker = extensionHostProcessWorker;
this.onDidCrash = this._extensionHostProcessWorker.onCrashed;
this._extensionHostProcessProxy = this._extensionHostProcessWorker.start().then(
(protocol) => {
return { value: this._createExtensionHostCustomers(protocol) };
},
(err) => {
console.error('Error received from starting extension host');
console.error(err);
return null;
}
);
this._extensionHostProcessProxy.then(() => {
initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent));
});
}
public dispose(): void {
if (this._extensionHostProcessWorker) {
this._extensionHostProcessWorker.dispose();
}
if (this._extensionHostProcessRPCProtocol) {
this._extensionHostProcessRPCProtocol.dispose();
}
for (let i = 0, len = this._extensionHostProcessCustomers.length; i < len; i++) {
const customer = this._extensionHostProcessCustomers[i];
try {
customer.dispose();
} catch (err) {
errors.onUnexpectedError(err);
}
}
this._extensionHostProcessProxy = null;
super.dispose();
}
public canProfileExtensionHost(): boolean {
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
}
private _createExtensionHostCustomers(protocol: IMessagePassingProtocol): ExtHostExtensionServiceShape {
let logger: IRPCProtocolLogger | null = null;
if (LOG_EXTENSION_HOST_COMMUNICATION || this._environmentService.logExtensionHostCommunication) {
logger = new RPCLogger();
}
this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol, logger);
this._register(this._extensionHostProcessRPCProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState)));
const extHostContext: IExtHostContext = {
remoteAuthority: this._remoteAuthority,
getProxy: <T>(identifier: ProxyIdentifier<T>): T => this._extensionHostProcessRPCProtocol.getProxy(identifier),
set: <T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R => this._extensionHostProcessRPCProtocol.set(identifier, instance),
assertRegistered: (identifiers: ProxyIdentifier<any>[]): void => this._extensionHostProcessRPCProtocol.assertRegistered(identifiers),
};
// Named customers
const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers();
for (let i = 0, len = namedCustomers.length; i < len; i++) {
const [id, ctor] = namedCustomers[i];
const instance = this._instantiationService.createInstance(ctor, extHostContext);
this._extensionHostProcessCustomers.push(instance);
this._extensionHostProcessRPCProtocol.set(id, instance);
}
// Customers
const customers = ExtHostCustomersRegistry.getCustomers();
for (let i = 0, len = customers.length; i < len; i++) {
const ctor = customers[i];
const instance = this._instantiationService.createInstance(ctor, extHostContext);
this._extensionHostProcessCustomers.push(instance);
}
// Check that no named customers are missing
const expected: ProxyIdentifier<any>[] = Object.keys(MainContext).map((key) => (<any>MainContext)[key]);
this._extensionHostProcessRPCProtocol.assertRegistered(expected);
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);
}
public activateByEvent(activationEvent: string): Thenable<void> {
if (this._extensionHostProcessFinishedActivateEvents[activationEvent] || !this._extensionHostProcessProxy) {
return NO_OP_VOID_PROMISE;
}
return this._extensionHostProcessProxy.then((proxy) => {
if (!proxy) {
// this case is already covered above and logged.
// i.e. the extension host could not be started
return NO_OP_VOID_PROMISE;
}
return proxy.value.$activateByEvent(activationEvent);
}).then(() => {
this._extensionHostProcessFinishedActivateEvents[activationEvent] = true;
});
}
public startExtensionHostProfile(): Promise<ProfileSession> {
if (this._extensionHostProcessWorker) {
let port = this._extensionHostProcessWorker.getInspectPort();
if (port) {
return this._instantiationService.createInstance(ExtensionHostProfiler, port).start();
}
}
throw new Error('Extension host not running or no inspect port available');
}
public getInspectPort(): number {
if (this._extensionHostProcessWorker) {
let port = this._extensionHostProcessWorker.getInspectPort();
if (port) {
return port;
}
}
return 0;
}
}
const colorTables = [
['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'],
['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD']
];
function prettyWithoutArrays(data: any): any {
if (Array.isArray(data)) {
return data;
}
if (data && typeof data === 'object' && typeof data.toString === 'function') {
let result = data.toString();
if (result !== '[object Object]') {
return result;
}
}
return data;
}
function pretty(data: any): any {
if (Array.isArray(data)) {
return data.map(prettyWithoutArrays);
}
return prettyWithoutArrays(data);
}
class RPCLogger implements IRPCProtocolLogger {
private _totalIncoming = 0;
private _totalOutgoing = 0;
private _log(direction: string, totalLength, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void {
data = pretty(data);
const colorTable = colorTables[initiator];
const color = LOG_USE_COLORS ? colorTable[req % colorTable.length] : '#000000';
let args = [`%c[${direction}]%c[${strings.pad(totalLength, 7, ' ')}]%c[len: ${strings.pad(msgLength, 5, ' ')}]%c${strings.pad(req, 5, ' ')} - ${str}`, 'color: darkgreen', 'color: grey', 'color: grey', `color: ${color}`];
if (/\($/.test(str)) {
args = args.concat(data);
args.push(')');
} else {
args.push(data);
}
console.log.apply(console, args);
}
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
this._totalIncoming += msgLength;
this._log('Ext \u2192 Win', this._totalIncoming, msgLength, req, initiator, str, data);
}
logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
this._totalOutgoing += msgLength;
this._log('Win \u2192 Ext', this._totalOutgoing, msgLength, req, initiator, str, data);
}
}

View file

@ -4,27 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as os from 'os';
import * as path from 'path';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { Barrier, runWhenIdle } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import * as objects from 'vs/base/common/objects';
import { Disposable } from 'vs/base/common/lifecycle';
import * as perf from 'vs/base/common/performance';
import * as platform from 'vs/base/common/platform';
import { fsPath, isEqualOrParent } from 'vs/base/common/resources';
import * as strings from 'vs/base/common/strings';
import { isEqualOrParent } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeDisabledNowKey, BetterMergeId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import pkg from 'vs/platform/node/package';
@ -33,254 +23,42 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
import { ActivationTimes, ExtensionPointContribution, IExtensionDescription, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionHostProcessWorker, IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, ILog, IRelaxedExtensionDescription, Translations } from 'vs/workbench/services/extensions/node/extensionPoints';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
const LOG_USE_COLORS = true;
let _SystemExtensionsRoot: string | null = null;
function getSystemExtensionsRoot(): string {
if (!_SystemExtensionsRoot) {
_SystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'extensions'));
}
return _SystemExtensionsRoot;
}
let _ExtraDevSystemExtensionsRoot: string | null = null;
function getExtraDevSystemExtensionsRoot(): string {
if (!_ExtraDevSystemExtensionsRoot) {
_ExtraDevSystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', '.build', 'builtInExtensions'));
}
return _ExtraDevSystemExtensionsRoot;
}
interface IBuiltInExtension {
name: string;
version: string;
repo: string;
}
interface IBuiltInExtensionControl {
[name: string]: 'marketplace' | 'disabled' | string;
}
class ExtraBuiltInExtensionResolver implements IExtensionResolver {
constructor(private builtInExtensions: IBuiltInExtension[], private control: IBuiltInExtensionControl) { }
resolveExtensions(): Promise<IExtensionReference[]> {
const result: IExtensionReference[] = [];
for (const ext of this.builtInExtensions) {
const controlState = this.control[ext.name] || 'marketplace';
switch (controlState) {
case 'disabled':
break;
case 'marketplace':
result.push({ name: ext.name, path: path.join(getExtraDevSystemExtensionsRoot(), ext.name) });
break;
default:
result.push({ name: ext.name, path: controlState });
break;
}
}
return Promise.resolve(result);
}
}
function messageWithSource(source: string, message: string): string {
if (source) {
return `[${source}]: ${message}`;
}
return message;
}
import { ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/electron-browser/extensionHostProcessManager';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
export class ExtensionHostProcessManager extends Disposable {
public readonly onDidCrash: Event<[number, string]>;
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
/**
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
*/
private readonly _extensionHostProcessFinishedActivateEvents: { [activationEvent: string]: boolean; };
private _extensionHostProcessRPCProtocol: RPCProtocol;
private readonly _extensionHostProcessCustomers: IDisposable[];
private readonly _extensionHostProcessWorker: IExtensionHostStarter;
/**
* winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object.
*/
private _extensionHostProcessProxy: Thenable<{ value: ExtHostExtensionServiceShape; }>;
constructor(
extensionHostProcessWorker: IExtensionHostStarter,
private readonly _remoteAuthority: string,
initialActivationEvents: string[],
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
) {
super();
this._extensionHostProcessFinishedActivateEvents = Object.create(null);
this._extensionHostProcessRPCProtocol = null;
this._extensionHostProcessCustomers = [];
this._extensionHostProcessWorker = extensionHostProcessWorker;
this.onDidCrash = this._extensionHostProcessWorker.onCrashed;
this._extensionHostProcessProxy = this._extensionHostProcessWorker.start().then(
(protocol) => {
return { value: this._createExtensionHostCustomers(protocol) };
},
(err) => {
console.error('Error received from starting extension host');
console.error(err);
return null;
}
);
this._extensionHostProcessProxy.then(() => {
initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent));
});
}
public dispose(): void {
if (this._extensionHostProcessWorker) {
this._extensionHostProcessWorker.dispose();
}
if (this._extensionHostProcessRPCProtocol) {
this._extensionHostProcessRPCProtocol.dispose();
}
for (let i = 0, len = this._extensionHostProcessCustomers.length; i < len; i++) {
const customer = this._extensionHostProcessCustomers[i];
try {
customer.dispose();
} catch (err) {
errors.onUnexpectedError(err);
}
}
this._extensionHostProcessProxy = null;
super.dispose();
}
public canProfileExtensionHost(): boolean {
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
}
private _createExtensionHostCustomers(protocol: IMessagePassingProtocol): ExtHostExtensionServiceShape {
let logger: IRPCProtocolLogger | null = null;
if (LOG_EXTENSION_HOST_COMMUNICATION || this._environmentService.logExtensionHostCommunication) {
logger = new RPCLogger();
}
this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol, logger);
this._register(this._extensionHostProcessRPCProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState)));
const extHostContext: IExtHostContext = {
remoteAuthority: this._remoteAuthority,
getProxy: <T>(identifier: ProxyIdentifier<T>): T => this._extensionHostProcessRPCProtocol.getProxy(identifier),
set: <T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R => this._extensionHostProcessRPCProtocol.set(identifier, instance),
assertRegistered: (identifiers: ProxyIdentifier<any>[]): void => this._extensionHostProcessRPCProtocol.assertRegistered(identifiers),
};
// Named customers
const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers();
for (let i = 0, len = namedCustomers.length; i < len; i++) {
const [id, ctor] = namedCustomers[i];
const instance = this._instantiationService.createInstance(ctor, extHostContext);
this._extensionHostProcessCustomers.push(instance);
this._extensionHostProcessRPCProtocol.set(id, instance);
}
// Customers
const customers = ExtHostCustomersRegistry.getCustomers();
for (let i = 0, len = customers.length; i < len; i++) {
const ctor = customers[i];
const instance = this._instantiationService.createInstance(ctor, extHostContext);
this._extensionHostProcessCustomers.push(instance);
}
// Check that no named customers are missing
const expected: ProxyIdentifier<any>[] = Object.keys(MainContext).map((key) => (<any>MainContext)[key]);
this._extensionHostProcessRPCProtocol.assertRegistered(expected);
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);
}
public activateByEvent(activationEvent: string): Thenable<void> {
if (this._extensionHostProcessFinishedActivateEvents[activationEvent] || !this._extensionHostProcessProxy) {
return NO_OP_VOID_PROMISE;
}
return this._extensionHostProcessProxy.then((proxy) => {
if (!proxy) {
// this case is already covered above and logged.
// i.e. the extension host could not be started
return NO_OP_VOID_PROMISE;
}
return proxy.value.$activateByEvent(activationEvent);
}).then(() => {
this._extensionHostProcessFinishedActivateEvents[activationEvent] = true;
});
}
public startExtensionHostProfile(): Promise<ProfileSession> {
if (this._extensionHostProcessWorker) {
let port = this._extensionHostProcessWorker.getInspectPort();
if (port) {
return this._instantiationService.createInstance(ExtensionHostProfiler, port).start();
}
}
throw new Error('Extension host not running or no inspect port available');
}
public getInspectPort(): number {
if (this._extensionHostProcessWorker) {
let port = this._extensionHostProcessWorker.getInspectPort();
if (port) {
return port;
}
}
return 0;
}
}
schema.properties.engines.properties.vscode.default = `^${pkg.version}`;
export class ExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: any;
private readonly _onDidRegisterExtensions: Emitter<void>;
private readonly _extensionHostLogsLocation: URI;
private _registry: ExtensionDescriptionRegistry;
private readonly _installedExtensionsReady: Barrier;
private readonly _isDev: boolean;
private readonly _extensionsMessages: { [id: string]: IMessage[] };
private _allRequestedActivateEvents: { [activationEvent: string]: boolean; };
private readonly _extensionScanner: CachedExtensionScanner;
private readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>({ leakWarningThreshold: 500 }));
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
private readonly _onDidChangeExtensionsStatus: Emitter<string[]> = this._register(new Emitter<string[]>());
public readonly onDidChangeExtensionsStatus: Event<string[]> = this._onDidChangeExtensionsStatus.event;
private _onWillActivateByEvent = new Emitter<IWillActivateEvent>();
readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.event;
private readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
public readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.event;
private readonly _onDidChangeResponsiveChange = new Emitter<IResponsiveStateChangeEvent>();
readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = this._onDidChangeResponsiveChange.event;
private readonly _onDidChangeResponsiveChange = this._register(new Emitter<IResponsiveStateChangeEvent>());
public readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = this._onDidChangeResponsiveChange.event;
// --- Members used per extension host process
private _extensionHostProcessManagers: ExtensionHostProcessManager[];
@ -295,8 +73,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IStorageService private readonly _storageService: IStorageService,
@IWindowService private readonly _windowService: IWindowService,
@ILifecycleService lifecycleService: ILifecycleService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService
) {
super();
this._extensionHostLogsLocation = URI.file(path.posix.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`));
@ -305,14 +83,13 @@ export class ExtensionService extends Disposable implements IExtensionService {
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
this._extensionsMessages = {};
this._allRequestedActivateEvents = Object.create(null);
this._onDidRegisterExtensions = new Emitter<void>({ leakWarningThreshold: 500 });
this._extensionScanner = this._instantiationService.createInstance(CachedExtensionScanner);
this._extensionHostProcessManagers = [];
this._extensionHostProcessActivationTimes = Object.create(null);
this._extensionHostExtensionRuntimeErrors = Object.create(null);
this._startDelayed(lifecycleService);
this._startDelayed(this._lifecycleService);
if (this._extensionEnablementService.allUserExtensionsDisabled) {
this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{
@ -348,10 +125,6 @@ export class ExtensionService extends Disposable implements IExtensionService {
this._onDidChangeResponsiveChange.dispose();
}
public get onDidRegisterExtensions(): Event<void> {
return this._onDidRegisterExtensions.event;
}
public restartExtensionHost(): void {
this._stopExtensionHostProcess();
this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
@ -546,8 +319,15 @@ export class ExtensionService extends Disposable implements IExtensionService {
// --- impl
private _scanAndHandleExtensions(): void {
this._extensionScanner.startScanningExtensions(new Logger((severity, source, message) => {
if (this._isDev && source) {
this._logOrShowMessage(severity, `[${source}]: ${message}`);
} else {
this._logOrShowMessage(severity, message);
}
}));
this._scanExtensions()
this._extensionScanner.scannedExtensions
.then(allExtensions => this._getRuntimeExtensions(allExtensions))
.then(allExtensions => {
this._registry = new ExtensionDescriptionRegistry(allExtensions);
@ -568,34 +348,6 @@ export class ExtensionService extends Disposable implements IExtensionService {
});
}
private _scanExtensions(): Promise<IExtensionDescription[]> {
const log = new Logger((severity, source, message) => {
this._logOrShowMessage(severity, this._isDev ? messageWithSource(source, message) : message);
});
return ExtensionService._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log)
.then(({ system, user, development }) => {
let result: { [extensionId: string]: IExtensionDescription; } = {};
system.forEach((systemExtension) => {
result[systemExtension.id] = systemExtension;
});
user.forEach((userExtension) => {
if (result.hasOwnProperty(userExtension.id)) {
log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionLocation.fsPath, userExtension.extensionLocation.fsPath));
}
result[userExtension.id] = userExtension;
});
development.forEach(developedExtension => {
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath));
if (result.hasOwnProperty(developedExtension.id)) {
log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionLocation.fsPath, developedExtension.extensionLocation.fsPath));
}
result[developedExtension.id] = developedExtension;
});
return Object.keys(result).map(name => result[name]);
});
}
private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise<IExtensionDescription[]> {
return this._extensionEnablementService.getDisabledExtensions()
.then(disabledExtensions => {
@ -648,7 +400,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
});
if (extensionsToDisable.length) {
return this.extensionManagementService.getInstalled(LocalExtensionType.User)
return this._extensionManagementService.getInstalled(LocalExtensionType.User)
.then(installed => {
const toDisable = installed.filter(i => extensionsToDisable.some(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i) }, e)));
return Promise.all(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled)));
@ -720,198 +472,6 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise<void> {
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, cacheKey);
const expected = JSON.parse(JSON.stringify(await ExtensionScanner.scanExtensions(input, new NullLogger())));
const cacheContents = await this._readExtensionCache(environmentService, cacheKey);
if (!cacheContents) {
// Cache has been deleted by someone else, which is perfectly fine...
return;
}
const actual = cacheContents.result;
if (objects.equals(expected, actual)) {
// Cache is valid and running with it is perfectly fine...
return;
}
try {
await pfs.del(cacheFile);
} catch (err) {
errors.onUnexpectedError(err);
console.error(err);
}
notificationService.prompt(
Severity.Error,
nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."),
[{
label: nls.localize('reloadWindow', "Reload Window"),
run: () => windowService.reloadWindow()
}]
);
}
private static async _readExtensionCache(environmentService: IEnvironmentService, cacheKey: string): Promise<IExtensionCacheData> {
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, cacheKey);
try {
const cacheRawContents = await pfs.readFile(cacheFile, 'utf8');
return JSON.parse(cacheRawContents);
} catch (err) {
// That's ok...
}
return null;
}
private static async _writeExtensionCache(environmentService: IEnvironmentService, cacheKey: string, cacheContents: IExtensionCacheData): Promise<void> {
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, cacheKey);
try {
await pfs.mkdirp(cacheFolder);
} catch (err) {
// That's ok...
}
try {
await pfs.writeFile(cacheFile, JSON.stringify(cacheContents));
} catch (err) {
// That's ok...
}
}
private static async _scanExtensionsWithCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise<IExtensionDescription[]> {
if (input.devMode) {
// Do not cache when running out of sources...
return ExtensionScanner.scanExtensions(input, log);
}
try {
const folderStat = await pfs.stat(input.absoluteFolderPath);
input.mtime = folderStat.mtime.getTime();
} catch (err) {
// That's ok...
}
const cacheContents = await this._readExtensionCache(environmentService, cacheKey);
if (cacheContents && cacheContents.input && ExtensionScannerInput.equals(cacheContents.input, input)) {
// Validate the cache asynchronously after 5s
setTimeout(async () => {
try {
await this._validateExtensionsCache(windowService, notificationService, environmentService, cacheKey, input);
} catch (err) {
errors.onUnexpectedError(err);
}
}, 5000);
return cacheContents.result.map((extensionDescription) => {
// revive URI object
(<IRelaxedExtensionDescription>extensionDescription).extensionLocation = URI.revive(extensionDescription.extensionLocation);
return extensionDescription;
});
}
const counterLogger = new CounterLogger(log);
const result = await ExtensionScanner.scanExtensions(input, counterLogger);
if (counterLogger.errorCnt === 0) {
// Nothing bad happened => cache the result
const cacheContents: IExtensionCacheData = {
input: input,
result: result
};
await this._writeExtensionCache(environmentService, cacheKey, cacheContents);
}
return result;
}
private static _scanInstalledExtensions(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, extensionEnablementService: IExtensionEnablementService, log: ILog): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
const translationConfig: Promise<Translations> = platform.translationsConfigFile
? pfs.readFile(platform.translationsConfigFile, 'utf8').then((content) => {
try {
return JSON.parse(content) as Translations;
} catch (err) {
return Object.create(null);
}
}, (err) => {
return Object.create(null);
})
: Promise.resolve(Object.create(null));
return translationConfig.then((translations) => {
const version = pkg.version;
const commit = product.commit;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.locale;
const builtinExtensions = this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
BUILTIN_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
log
);
let finalBuiltinExtensions: Promise<IExtensionDescription[]> = builtinExtensions;
if (devMode) {
const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json'));
const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8')
.then<IBuiltInExtension[]>(raw => JSON.parse(raw));
const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
const controlFile = pfs.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
finalBuiltinExtensions = ExtensionScanner.mergeBuiltinExtensions(builtinExtensions, extraBuiltinExtensions);
}
const userExtensions = (
extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath
? Promise.resolve([])
: this._scanExtensionsWithCache(
windowService,
notificationService,
environmentService,
USER_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
log
)
);
// Always load developed extensions while extensions development
let developedExtensions = Promise.resolve([]);
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) {
developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, fsPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log
);
}
return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => {
const system = extensionDescriptions[0];
const user = extensionDescriptions[1];
const development = extensionDescriptions[2];
return { system, user, development };
}).then(null, err => {
log.error('', err);
return { system: [], user: [], development: [] };
});
});
}
private static _handleExtensionPoint<T>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void {
let users: IExtensionPointUser<T>[] = [], usersLen = 0;
for (let i = 0, len = availableExtensions.length; i < len; i++) {
@ -983,118 +543,3 @@ export class ExtensionService extends Disposable implements IExtensionService {
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
}
const colorTables = [
['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'],
['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD']
];
function prettyWithoutArrays(data: any): any {
if (Array.isArray(data)) {
return data;
}
if (data && typeof data === 'object' && typeof data.toString === 'function') {
let result = data.toString();
if (result !== '[object Object]') {
return result;
}
}
return data;
}
function pretty(data: any): any {
if (Array.isArray(data)) {
return data.map(prettyWithoutArrays);
}
return prettyWithoutArrays(data);
}
class RPCLogger implements IRPCProtocolLogger {
private _totalIncoming = 0;
private _totalOutgoing = 0;
private _log(direction: string, totalLength, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void {
data = pretty(data);
const colorTable = colorTables[initiator];
const color = LOG_USE_COLORS ? colorTable[req % colorTable.length] : '#000000';
let args = [`%c[${direction}]%c[${strings.pad(totalLength, 7, ' ')}]%c[len: ${strings.pad(msgLength, 5, ' ')}]%c${strings.pad(req, 5, ' ')} - ${str}`, 'color: darkgreen', 'color: grey', 'color: grey', `color: ${color}`];
if (/\($/.test(str)) {
args = args.concat(data);
args.push(')');
} else {
args.push(data);
}
console.log.apply(console, args);
}
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
this._totalIncoming += msgLength;
this._log('Ext \u2192 Win', this._totalIncoming, msgLength, req, initiator, str, data);
}
logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
this._totalOutgoing += msgLength;
this._log('Win \u2192 Ext', this._totalOutgoing, msgLength, req, initiator, str, data);
}
}
interface IExtensionCacheData {
input: ExtensionScannerInput;
result: IExtensionDescription[];
}
export class Logger implements ILog {
private readonly _messageHandler: (severity: Severity, source: string, message: string) => void;
constructor(
messageHandler: (severity: Severity, source: string, message: string) => void
) {
this._messageHandler = messageHandler;
}
public error(source: string, message: string): void {
this._messageHandler(Severity.Error, source, message);
}
public warn(source: string, message: string): void {
this._messageHandler(Severity.Warning, source, message);
}
public info(source: string, message: string): void {
this._messageHandler(Severity.Info, source, message);
}
}
class CounterLogger implements ILog {
public errorCnt = 0;
public warnCnt = 0;
public infoCnt = 0;
constructor(private readonly _actual: ILog) {
}
public error(source: string, message: string): void {
this._actual.error(source, message);
}
public warn(source: string, message: string): void {
this._actual.warn(source, message);
}
public info(source: string, message: string): void {
this._actual.info(source, message);
}
}
class NullLogger implements ILog {
public error(source: string, message: string): void {
}
public warn(source: string, message: string): void {
}
public info(source: string, message: string): void {
}
}