Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -0,0 +1,372 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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 | null> {
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<IExtensionDescription[]> = Promise.resolve([]);
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI && 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

@@ -3,41 +3,73 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import * as objects from 'vs/base/common/objects';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { findFreePort } from 'vs/base/node/ports';
import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ChildProcess, fork } from 'child_process';
import { ipcRenderer as ipc } from 'electron';
import product from 'vs/platform/node/product';
import { Server, Socket, createServer } from 'net';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { timeout } from 'vs/base/common/async';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event, anyEvent, debounceEvent, fromNodeEventEmitter, mapEvent } from 'vs/base/common/event';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import * as objects from 'vs/base/common/objects';
import { isWindows } from 'vs/base/common/platform';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console';
import { findFreePort, randomPort } from 'vs/base/node/ports';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { Protocol, generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
import { IBroadcast, IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService';
import { getScopes } from 'vs/platform/configuration/common/configurationRegistry';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { generateRandomPipeName, Protocol } from 'vs/base/parts/ipc/node/ipc.net';
import { createServer, Server, Socket } from 'net';
import { Event, Emitter, debounceEvent, mapEvent, anyEvent, fromNodeEventEmitter } from 'vs/base/common/event';
import { IInitData, IWorkspaceData, IConfigurationInitData } from 'vs/workbench/api/node/extHost.protocol';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/node/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IConfigurationInitData, IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ICrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService';
import { IBroadcastService, IBroadcast } from 'vs/platform/broadcast/electron-browser/broadcastService';
import { isEqual } from 'vs/base/common/paths';
import { EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console';
import { getScopes } from 'vs/platform/configuration/common/configurationRegistry';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
export class ExtensionHostProcessWorker {
export interface IExtensionHostStarter {
readonly onCrashed: Event<[number, string]>;
start(): Thenable<IMessagePassingProtocol>;
getInspectPort(): number;
dispose(): void;
}
export interface IExtensionDevOptions {
readonly isExtensionDevHost: boolean;
readonly isExtensionDevDebug: boolean;
readonly isExtensionDevDebugBrk: boolean;
readonly isExtensionDevTestFromCli: boolean;
}
export function parseExtensionDevOptions(environmentService: IEnvironmentService): IExtensionDevOptions {
// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
let isExtensionDevHost = environmentService.isExtensionDevelopment;
const extDevLoc = environmentService.extensionDevelopmentLocationURI;
const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file;
let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number';
let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break;
let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsPath && !environmentService.debugExtensionHost.break;
return {
isExtensionDevHost,
isExtensionDevDebug,
isExtensionDevDebugBrk,
isExtensionDevTestFromCli,
};
}
export class ExtensionHostProcessWorker implements IExtensionHostStarter {
private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>();
public readonly onCrashed: Event<[number, string]> = this._onCrashed.event;
@@ -58,10 +90,12 @@ export class ExtensionHostProcessWorker {
private _inspectPort: number;
private _extensionHostProcess: ChildProcess;
private _extensionHostConnection: Socket;
private _messageProtocol: TPromise<IMessagePassingProtocol>;
private _messageProtocol: Promise<IMessagePassingProtocol>;
constructor(
private readonly _extensions: TPromise<IExtensionDescription[]>,
private readonly _autoStart: boolean,
private readonly _extensions: Promise<IExtensionDescription[]>,
private readonly _extensionHostLogsLocation: URI,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@INotificationService private readonly _notificationService: INotificationService,
@IWindowsService private readonly _windowsService: IWindowsService,
@@ -72,13 +106,14 @@ export class ExtensionHostProcessWorker {
@IWorkspaceConfigurationService private readonly _configurationService: IWorkspaceConfigurationService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ICrashReporterService private readonly _crashReporterService: ICrashReporterService,
@ILogService private readonly _logService: ILogService
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService
) {
// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
this._isExtensionDevHost = this._environmentService.isExtensionDevelopment;
this._isExtensionDevDebug = (typeof this._environmentService.debugExtensionHost.port === 'number');
this._isExtensionDevDebugBrk = !!this._environmentService.debugExtensionHost.break;
this._isExtensionDevTestFromCli = this._isExtensionDevHost && !!this._environmentService.extensionTestsPath && !this._environmentService.debugExtensionHost.break;
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
this._isExtensionDevDebug = devOpts.isExtensionDevDebug;
this._isExtensionDevDebugBrk = devOpts.isExtensionDevDebugBrk;
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
this._lastExtensionHostError = null;
this._terminating = false;
@@ -90,7 +125,7 @@ export class ExtensionHostProcessWorker {
this._toDispose = [];
this._toDispose.push(this._onCrashed);
this._toDispose.push(this._lifecycleService.onWillShutdown((e) => this._onWillShutdown(e)));
this._toDispose.push(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e)));
this._toDispose.push(this._lifecycleService.onShutdown(reason => this.terminate()));
this._toDispose.push(this._broadcastService.onBroadcast(b => this._onBroadcast(b)));
@@ -114,28 +149,28 @@ export class ExtensionHostProcessWorker {
// Close Ext Host Window Request
if (broadcast.channel === EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL && this._isExtensionDevHost) {
const extensionPaths = broadcast.payload as string[];
if (Array.isArray(extensionPaths) && extensionPaths.some(path => isEqual(this._environmentService.extensionDevelopmentPath, path, !isLinux))) {
const extensionLocations = broadcast.payload as string[];
if (Array.isArray(extensionLocations) && extensionLocations.some(uriString => isEqual(this._environmentService.extensionDevelopmentLocationURI, URI.parse(uriString)))) {
this._windowService.closeWindow();
}
}
if (broadcast.channel === EXTENSION_RELOAD_BROADCAST_CHANNEL && this._isExtensionDevHost) {
const extensionPaths = broadcast.payload as string[];
if (Array.isArray(extensionPaths) && extensionPaths.some(path => isEqual(this._environmentService.extensionDevelopmentPath, path, !isLinux))) {
if (Array.isArray(extensionPaths) && extensionPaths.some(uriString => isEqual(this._environmentService.extensionDevelopmentLocationURI, URI.parse(uriString)))) {
this._windowService.reloadWindow();
}
}
}
public start(): TPromise<IMessagePassingProtocol> {
public start(): Promise<IMessagePassingProtocol> {
if (this._terminating) {
// .terminate() was called
return null;
}
if (!this._messageProtocol) {
this._messageProtocol = TPromise.join([this._tryListenOnPipe(), this._tryFindDebugPort()]).then(data => {
this._messageProtocol = Promise.all([this._tryListenOnPipe(), this._tryFindDebugPort()]).then(data => {
const pipeName = data[0];
const portData = data[1];
@@ -176,7 +211,7 @@ export class ExtensionHostProcessWorker {
}
// Run Extension Host as fork of current process
this._extensionHostProcess = fork(URI.parse(require.toUrl('bootstrap')).fsPath, ['--type=extensionHost'], opts);
this._extensionHostProcess = fork(getPathFromAmdModule(require, 'bootstrap-fork'), ['--type=extensionHost'], opts);
// Catch all output coming from the extension host process
type Output = { data: string, format: string[] };
@@ -220,7 +255,7 @@ export class ExtensionHostProcessWorker {
this._extensionHostProcess.on('exit', (code: number, signal: string) => this._onExtHostProcessExit(code, signal));
// Notify debugger that we are ready to attach to the process if we run a development extension
if (this._isExtensionDevHost && portData.actual) {
if (this._isExtensionDevHost && portData.actual && this._isExtensionDevDebug) {
this._broadcastService.broadcast({
channel: EXTENSION_ATTACH_BROADCAST_CHANNEL,
payload: {
@@ -232,8 +267,8 @@ export class ExtensionHostProcessWorker {
this._inspectPort = portData.actual;
// Help in case we fail to start it
let startupTimeoutHandle: number;
if (!this._environmentService.isBuilt || this._isExtensionDevHost) {
let startupTimeoutHandle: any;
if (!this._environmentService.isBuilt && !this._windowService.getConfiguration().remoteAuthority || this._isExtensionDevHost) {
startupTimeoutHandle = setTimeout(() => {
const msg = this._isExtensionDevDebugBrk
? nls.localize('extensionHostProcess.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.")
@@ -243,7 +278,8 @@ export class ExtensionHostProcessWorker {
[{
label: nls.localize('reloadWindow', "Reload Window"),
run: () => this._windowService.reloadWindow()
}]
}],
{ sticky: true }
);
}, 10000);
}
@@ -262,8 +298,8 @@ export class ExtensionHostProcessWorker {
/**
* Start a server (`this._namedPipeServer`) that listens on a named pipe and return the named pipe name.
*/
private _tryListenOnPipe(): TPromise<string> {
return new TPromise<string>((resolve, reject) => {
private _tryListenOnPipe(): Promise<string> {
return new Promise<string>((resolve, reject) => {
const pipeName = generateRandomPipeName();
this._namedPipeServer = createServer();
@@ -278,15 +314,13 @@ export class ExtensionHostProcessWorker {
/**
* Find a free port if extension host debugging is enabled.
*/
private _tryFindDebugPort(): TPromise<{ expected: number; actual: number }> {
private _tryFindDebugPort(): Promise<{ expected: number; actual: number }> {
let expected: number;
let startPort = 9333;
let startPort = randomPort();
if (typeof this._environmentService.debugExtensionHost.port === 'number') {
startPort = expected = this._environmentService.debugExtensionHost.port;
} else {
return TPromise.as({ expected: undefined, actual: 0 });
}
return new TPromise((c, e) => {
return new Promise(resolve => {
return findFreePort(startPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */).then(port => {
if (!port) {
console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color: black');
@@ -300,14 +334,14 @@ export class ExtensionHostProcessWorker {
console.info(`%c[Extension Host] %cdebugger listening on port ${port}`, 'color: blue', 'color: black');
}
}
return c({ expected, actual: port });
return resolve({ expected, actual: port });
});
});
}
private _tryExtHostHandshake(): TPromise<IMessagePassingProtocol> {
private _tryExtHostHandshake(): Promise<IMessagePassingProtocol> {
return new TPromise<IMessagePassingProtocol>((resolve, reject) => {
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
// Wait for the extension host to connect to our named pipe
// and wrap the socket in the message passing protocol
@@ -329,30 +363,49 @@ export class ExtensionHostProcessWorker {
// 1) wait for the incoming `ready` event and send the initialization data.
// 2) wait for the incoming `initialized` event.
return new TPromise<IMessagePassingProtocol>((resolve, reject) => {
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
let handle = setTimeout(() => {
reject('timeout');
}, 60 * 1000);
let timeoutHandle: NodeJS.Timer;
const installTimeoutCheck = () => {
timeoutHandle = setTimeout(() => {
reject('timeout');
}, 60 * 1000);
};
const uninstallTimeoutCheck = () => {
clearTimeout(timeoutHandle);
};
// Wait 60s for the ready message
installTimeoutCheck();
const disposable = protocol.onMessage(msg => {
if (msg === 'ready') {
if (isMessageOfType(msg, MessageType.Ready)) {
// 1) Extension Host is ready to receive messages, initialize it
this._createExtHostInitData().then(data => protocol.send(JSON.stringify(data)));
uninstallTimeoutCheck();
this._createExtHostInitData().then(data => {
// Wait 60s for the initialized message
installTimeoutCheck();
protocol.send(Buffer.from(JSON.stringify(data)));
});
return;
}
if (msg === 'initialized') {
if (isMessageOfType(msg, MessageType.Initialized)) {
// 2) Extension Host is initialized
clearTimeout(handle);
uninstallTimeoutCheck();
// stop listening for messages here
disposable.dispose();
// release this promise
resolve(protocol);
// using a buffered message protocol here because between now
// and the first time a `then` executes some messages might be lost
// unless we immediately register a listener for `onMessage`.
resolve(new BufferedMessagePassingProtocol(protocol));
return;
}
@@ -364,29 +417,38 @@ export class ExtensionHostProcessWorker {
});
}
private _createExtHostInitData(): TPromise<IInitData> {
return TPromise.join([this._telemetryService.getTelemetryInfo(), this._extensions]).then(([telemetryInfo, extensionDescriptions]) => {
const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} };
const r: IInitData = {
parentPid: process.pid,
environment: {
isExtensionDevelopmentDebug: this._isExtensionDevDebug,
appRoot: this._environmentService.appRoot,
appSettingsHome: this._environmentService.appSettingsHome,
extensionDevelopmentPath: this._environmentService.extensionDevelopmentPath,
extensionTestsPath: this._environmentService.extensionTestsPath
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : <IWorkspaceData>this._contextService.getWorkspace(),
extensions: extensionDescriptions,
// Send configurations scopes only in development mode.
configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData,
telemetryInfo,
windowId: this._windowService.getCurrentWindowId(),
logLevel: this._logService.getLevel(),
logsPath: this._environmentService.logsPath
};
return r;
});
private _createExtHostInitData(): Promise<IInitData> {
return Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions])
.then(([telemetryInfo, extensionDescriptions]) => {
const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} };
const workspace = this._contextService.getWorkspace();
const r: IInitData = {
commit: product.commit,
parentPid: process.pid,
environment: {
isExtensionDevelopmentDebug: this._isExtensionDevDebug,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : void 0,
appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : void 0,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsPath: this._environmentService.extensionTestsPath,
globalStorageHome: URI.file(this._environmentService.globalStorageHome)
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
configuration: workspace.configuration,
folders: workspace.folders,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
},
extensions: extensionDescriptions,
// Send configurations scopes only in development mode.
configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData,
telemetryInfo,
logLevel: this._logService.getLevel(),
logsLocation: this._extensionHostLogsLocation,
autoStart: this._autoStart
};
return r;
});
}
private _logExtensionHostMessage(entry: IRemoteConsoleLog) {
@@ -467,13 +529,11 @@ export class ExtensionHostProcessWorker {
// Send the extension host a request to terminate itself
// (graceful termination)
protocol.send({
type: '__$terminate'
});
protocol.send(createMessageOfType(MessageType.Terminate));
// Give the extension host 60s, after which we will
// Give the extension host 10s, after which we will
// try to kill the process and release any resources
setTimeout(() => this._cleanResources(), 60 * 1000);
setTimeout(() => this._cleanResources(), 10 * 1000);
}, (err) => {
@@ -498,7 +558,7 @@ export class ExtensionHostProcessWorker {
}
}
private _onWillShutdown(event: ShutdownEvent): void {
private _onWillShutdown(event: WillShutdownEvent): void {
// If the extension development host was started without debugger attached we need
// to communicate this back to the main side to terminate the debug session
@@ -510,7 +570,61 @@ export class ExtensionHostProcessWorker {
}
});
event.veto(TPromise.timeout(100 /* wait a bit for IPC to get delivered */).then(() => false));
event.join(timeout(100 /* wait a bit for IPC to get delivered */));
}
}
}
/**
* Will ensure no messages are lost from creation time until the first user of onMessage comes in.
*/
class BufferedMessagePassingProtocol implements IMessagePassingProtocol {
private readonly _actual: IMessagePassingProtocol;
private _bufferedMessagesListener: IDisposable;
private _bufferedMessages: Buffer[];
constructor(actual: IMessagePassingProtocol) {
this._actual = actual;
this._bufferedMessages = [];
this._bufferedMessagesListener = this._actual.onMessage((buff) => this._bufferedMessages.push(buff));
}
public send(buffer: Buffer): void {
this._actual.send(buffer);
}
public onMessage(listener: (e: Buffer) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable {
if (!this._bufferedMessages) {
// second caller gets nothing
return this._actual.onMessage(listener, thisArgs, disposables);
}
// prepare result
const result = this._actual.onMessage(listener, thisArgs, disposables);
// stop listening to buffered messages
this._bufferedMessagesListener.dispose();
// capture buffered messages
const bufferedMessages = this._bufferedMessages;
this._bufferedMessages = null;
// it is important to deliver these messages after this call, but before
// other messages have a chance to be received (to guarantee in order delivery)
// that's why we're using here nextTick and not other types of timeouts
process.nextTick(() => {
// deliver buffered messages
while (bufferedMessages.length > 0) {
const msg = bufferedMessages.shift();
try {
listener.call(thisArgs, msg);
} catch (e) {
onUnexpectedError(e);
}
}
});
return result;
}
}

View File

@@ -0,0 +1,245 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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';
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
// 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();
}
// {{SQL CARBON EDIT}}
public getExtenstionHostProcessWorker(): IExtensionHostStarter {
return this._extensionHostProcessWorker;
}
// {{SQL CARBON EDIT}} - End
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;
}
public resolveAuthority(remoteAuthority: string): Thenable<ResolvedAuthority> {
return this._extensionHostProcessProxy.then(proxy => proxy.value.$resolveAuthority(remoteAuthority));
}
public start(enabledExtensionIds: string[]): Thenable<void> {
return this._extensionHostProcessProxy.then(proxy => proxy.value.$startExtensionHost(enabledExtensionIds));
}
}
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

@@ -3,33 +3,26 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IExtensionService, IExtensionDescription, ProfileSession, IExtensionHostProfile, ProfileSegmentId } from 'vs/workbench/services/extensions/common/extensions';
import { TPromise } from 'vs/base/common/winjs.base';
import { Profile, ProfileNode } from 'v8-inspect-profiler';
import { TernarySearchTree } from 'vs/base/common/map';
import { realpathSync } from 'vs/base/node/extfs';
import { Profile, ProfileNode } from 'v8-inspect-profiler';
import { IExtensionDescription, IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
export class ExtensionHostProfiler {
constructor(private readonly _port: number, @IExtensionService private readonly _extensionService: IExtensionService) {
}
public start(): TPromise<ProfileSession> {
return TPromise.wrap(import('v8-inspect-profiler')).then(profiler => {
return profiler.startProfiling({ port: this._port }).then(session => {
return {
stop: () => {
return TPromise.wrap(session.stop()).then(profile => {
return this._extensionService.getExtensions().then(extensions => {
return this.distill(profile.profile, extensions);
});
});
}
};
});
});
public async start(): Promise<ProfileSession> {
const profiler = await import('v8-inspect-profiler');
const session = await profiler.startProfiling({ port: this._port });
return {
stop: async () => {
const profile = await session.stop();
const extensions = await this._extensionService.getExtensions();
return this.distill((profile as any).profile, extensions);
}
};
}
private distill(profile: Profile, extensions: IExtensionDescription[]): IExtensionHostProfile {

View File

@@ -2,264 +2,63 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import * as errors from 'vs/base/common/errors';
import * as objects from 'vs/base/common/objects';
import { TPromise } from 'vs/base/common/winjs.base';
import pkg from 'vs/platform/node/package';
import * as path from 'path';
import * as os from 'os';
import * as pfs from 'vs/base/node/pfs';
import URI from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
import { IMessage, IExtensionDescription, IExtensionsStatus, IExtensionService, ExtensionPointContribution, ActivationTimes, ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
import { USER_MANIFEST_CACHE_FILE, BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER } from 'vs/platform/extensions/common/extensions';
import { IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionManagementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, BetterMergeId, BetterMergeDisabledNowKey, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionScanner, ILog, ExtensionScannerInput, IExtensionResolver, IExtensionReference, Translations, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { Barrier, runWhenIdle } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { mark } from 'vs/base/common/performance';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { Barrier } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import * as strings from 'vs/base/common/strings';
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { Schemas } from 'vs/base/common/network';
let _SystemExtensionsRoot: string = null;
function getSystemExtensionsRoot(): string {
if (!_SystemExtensionsRoot) {
_SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
}
return _SystemExtensionsRoot;
}
let _ExtraDevSystemExtensionsRoot: string = null;
function getExtraDevSystemExtensionsRoot(): string {
if (!_ExtraDevSystemExtensionsRoot) {
_ExtraDevSystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', '.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(): TPromise<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 TPromise.as(result);
}
}
// Enable to see detailed message communication between window and extension host
const logExtensionHostCommunication = false;
function messageWithSource(source: string, message: string): string {
if (source) {
return `[${source}]: ${message}`;
}
return message;
}
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
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 } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
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 = TPromise.wrap<void>(void 0);
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
export class ExtensionHostProcessManager extends Disposable {
public readonly onDidCrash: Event<[number, string]>;
/**
* 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: ExtensionHostProcessWorker;
/**
* winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object.
*/
private _extensionHostProcessProxy: TPromise<{ value: ExtHostExtensionServiceShape; }>;
constructor(
extensionHostProcessWorker: ExtensionHostProcessWorker,
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));
});
}
// {{SQL CARBON EDIT}}
public getExtenstionHostProcessWorker(): ExtensionHostProcessWorker {
return this._extensionHostProcessWorker;
}
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 {
if (logExtensionHostCommunication || this._environmentService.logExtensionHostCommunication) {
protocol = asLoggingProtocol(protocol);
}
this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol);
const extHostContext: IExtHostContext = {
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): TPromise<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(): TPromise<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');
}
}
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 readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
public readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.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[];
private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; };
@@ -271,25 +70,24 @@ export class ExtensionService extends Disposable implements IExtensionService {
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@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()}`));
this._registry = null;
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
this._extensionsMessages = {};
this._allRequestedActivateEvents = Object.create(null);
this._onDidRegisterExtensions = new Emitter<void>();
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."), [{
@@ -301,58 +99,37 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
// {{SQL CARBON EDIT}}
public getExtenstionHostProcessId(): number {
if (this._extensionHostProcessManagers.length !== 1)
{
this._logOrShowMessage(Severity.Warning, 'Exactly one Extension Host Process Manager was expected');
}
return this._extensionHostProcessManagers[0].getExtenstionHostProcessWorker().getExtenstionHostProcess().pid;
}
private startDelayed(lifecycleService: ILifecycleService): void {
let started = false;
const startOnce = () => {
if (!started) {
started = true;
this._startExtensionHostProcess([]);
this._scanAndHandleExtensions();
}
};
private _startDelayed(lifecycleService: ILifecycleService): void {
// delay extension host creation and extension scanning
// until the workbench is restoring. we cannot defer the
// extension host more (LifecyclePhase.Running) because
// until the workbench is running. we cannot defer the
// extension host more (LifecyclePhase.Restored) because
// some editors require the extension host to restore
// and this would result in a deadlock
// see https://github.com/Microsoft/vscode/issues/41322
lifecycleService.when(LifecyclePhase.Restoring).then(() => {
// we add an additional delay of 800ms because the extension host
// starting is a potential expensive operation and we do no want
// to fight with editors, viewlets and panels restoring.
setTimeout(() => startOnce(), 800);
lifecycleService.when(LifecyclePhase.Ready).then(() => {
// reschedule to ensure this runs after restoring viewlets, panels, and editors
runWhenIdle(() => {
perf.mark('willLoadExtensions');
this._startExtensionHostProcess(true, []);
this._scanAndHandleExtensions();
this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions'));
}, 50 /*max delay*/);
});
// if we are running before the 800ms delay, make sure to start
// the extension host right away though.
lifecycleService.when(LifecyclePhase.Running).then(() => startOnce());
}
public dispose(): void {
super.dispose();
}
public get onDidRegisterExtensions(): Event<void> {
return this._onDidRegisterExtensions.event;
this._onWillActivateByEvent.dispose();
this._onDidChangeResponsiveChange.dispose();
}
public restartExtensionHost(): void {
this._stopExtensionHostProcess();
this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents));
}
public startExtensionHost(): void {
this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents));
}
public stopExtensionHost(): void {
@@ -374,12 +151,13 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
private _startExtensionHostProcess(initialActivationEvents: string[]): void {
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
this._stopExtensionHostProcess();
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, this.getExtensions());
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, initialActivationEvents);
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, !isInitialStart, this.getExtensions(), this._extensionHostLogsLocation);
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal));
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ target: extHostProcessManager, isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(extHostProcessManager);
}
@@ -387,6 +165,23 @@ export class ExtensionService extends Disposable implements IExtensionService {
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
this._stopExtensionHostProcess();
if (code === 55) {
this._notificationService.prompt(
Severity.Error,
nls.localize('extensionHostProcess.versionMismatchCrash', "Extension host cannot start: version mismatch."),
[{
label: nls.localize('relaunch', "Relaunch VS Code"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const windowsService = accessor.get(IWindowsService);
windowsService.relaunch({});
});
}
}]
);
return;
}
let message = nls.localize('extensionHostProcess.crash', "Extension host terminated unexpectedly.");
if (code === 87) {
message = nls.localize('extensionHostProcess.unresponsiveCrash', "Extension host terminated because it was not responsive.");
@@ -399,14 +194,14 @@ export class ExtensionService extends Disposable implements IExtensionService {
},
{
label: nls.localize('restart', "Restart Extension Host"),
run: () => this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents))
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
}]
);
}
// ---- begin IExtensionService
public activateByEvent(activationEvent: string): TPromise<void> {
public activateByEvent(activationEvent: string): Promise<void> {
if (this._installedExtensionsReady.isOpen()) {
// Extensions have been scanned and interpreted
@@ -429,23 +224,34 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
private _activateByEvent(activationEvent: string): TPromise<void> {
return TPromise.join(
private _activateByEvent(activationEvent: string): Promise<void> {
const result = Promise.all(
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent))
).then(() => { });
this._onWillActivateByEvent.fire({
event: activationEvent,
activation: result
});
return result;
}
public whenInstalledExtensionsRegistered(): TPromise<boolean> {
public whenInstalledExtensionsRegistered(): Promise<boolean> {
return this._installedExtensionsReady.wait();
}
public getExtensions(): TPromise<IExtensionDescription[]> {
public getExtensions(): Promise<IExtensionDescription[]> {
return this._installedExtensionsReady.wait().then(() => {
return this._registry.getAllExtensionDescriptions();
});
}
public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): TPromise<ExtensionPointContribution<T>[]> {
public getExtension(id: string): Promise<IExtensionDescription | undefined> {
return this._installedExtensionsReady.wait().then(() => {
return this._registry.getExtensionDescription(id);
});
}
public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> {
return this._installedExtensionsReady.wait().then(() => {
let availableExtensions = this._registry.getAllExtensionDescriptions();
@@ -489,7 +295,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
return false;
}
public startExtensionHostProfile(): TPromise<ProfileSession> {
public startExtensionHostProfile(): Promise<ProfileSession> {
for (let i = 0, len = this._extensionHostProcessManagers.length; i < len; i++) {
const extHostProcessManager = this._extensionHostProcessManagers[i];
if (extHostProcessManager.canProfileExtensionHost()) {
@@ -499,59 +305,49 @@ export class ExtensionService extends Disposable implements IExtensionService {
throw new Error('Extension host not running or no inspect port available');
}
public getInspectPort(): number {
if (this._extensionHostProcessManagers.length > 0) {
return this._extensionHostProcessManagers[0].getInspectPort();
}
return 0;
}
// ---- end IExtensionService
// --- impl
private _scanAndHandleExtensions(): void {
private async _scanAndHandleExtensions(): Promise<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()
.then(allExtensions => this._getRuntimeExtensions(allExtensions))
.then(allExtensions => {
this._registry = new ExtensionDescriptionRegistry(allExtensions);
let availableExtensions = this._registry.getAllExtensionDescriptions();
let extensionPoints = ExtensionsRegistry.getExtensionPoints();
let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
for (let i = 0, len = extensionPoints.length; i < len; i++) {
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
}
mark('extensionHostReady');
this._installedExtensionsReady.open();
this._onDidRegisterExtensions.fire(void 0);
this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id));
});
const extensionHost = this._extensionHostProcessManagers[0];
const extensions = await this._extensionScanner.scannedExtensions;
const enabledExtensions = await this._getRuntimeExtensions(extensions);
extensionHost.start(enabledExtensions.map(extension => extension.id));
this._onHasExtensions(enabledExtensions);
}
private _scanExtensions(): TPromise<IExtensionDescription[]> {
const log = new Logger((severity, source, message) => {
this._logOrShowMessage(severity, this._isDev ? messageWithSource(source, message) : message);
});
private _onHasExtensions(allExtensions: IExtensionDescription[]): void {
this._registry = new ExtensionDescriptionRegistry(allExtensions);
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]);
});
let availableExtensions = this._registry.getAllExtensionDescriptions();
let extensionPoints = ExtensionsRegistry.getExtensionPoints();
let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
for (let i = 0, len = extensionPoints.length; i < len; i++) {
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
}
perf.mark('extensionHostReady');
this._installedExtensionsReady.open();
this._onDidRegisterExtensions.fire(void 0);
this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id));
}
private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise<IExtensionDescription[]> {
@@ -564,12 +360,23 @@ export class ExtensionService extends Disposable implements IExtensionService {
const enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || [];
const notFound = (id: string) => nls.localize('notFound', "Extension \`{0}\` cannot use PROPOSED API as it cannot be found", id);
if (enableProposedApiFor.length) {
let allProposed = (enableProposedApiFor instanceof Array ? enableProposedApiFor : [enableProposedApiFor]);
allProposed.forEach(id => {
if (!allExtensions.some(description => description.id === id)) {
console.error(notFound(id));
}
});
}
const enableProposedApiForAll = !this._environmentService.isBuilt ||
(!!this._environmentService.extensionDevelopmentPath && product.nameLong.indexOf('Insiders') >= 0) ||
(!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong.indexOf('Insiders') >= 0) ||
(enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args);
for (const extension of allExtensions) {
const isExtensionUnderDevelopment = this._environmentService.isExtensionDevelopment && extension.extensionLocation.scheme === Schemas.file && extension.extensionLocation.fsPath.indexOf(this._environmentService.extensionDevelopmentPath) === 0;
const isExtensionUnderDevelopment = this._environmentService.isExtensionDevelopment && isEqualOrParent(extension.extensionLocation, this._environmentService.extensionDevelopmentLocationURI);
// Do not disable extensions under development
if (!isExtensionUnderDevelopment) {
if (disabledExtensions.some(disabled => areSameExtensions(disabled, extension))) {
@@ -595,13 +402,12 @@ 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 TPromise.join(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled)));
return Promise.all(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled)));
})
.then(() => {
this._storageService.store(BetterMergeDisabledNowKey, true);
return runtimeExtensions;
});
} else {
@@ -611,7 +417,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription {
if (!isFalsyOrEmpty(product.extensionAllowedProposedApi)
if (isNonEmptyArray(product.extensionAllowedProposedApi)
&& product.extensionAllowedProposedApi.indexOf(extension.id) >= 0
) {
// fast lane -> proposed api is available to all extensions
@@ -667,222 +473,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): TPromise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
const translationConfig: TPromise<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);
})
: TPromise.as(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: TPromise<IExtensionDescription[]> = TPromise.wrap(builtinExtensions);
if (devMode) {
const builtInExtensionsFilePath = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', '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 = TPromise.join([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
finalBuiltinExtensions = TPromise.join([builtinExtensions, extraBuiltinExtensions]).then(([builtinExtensions, extraBuiltinExtensions]) => {
let resultMap: { [id: string]: IExtensionDescription; } = Object.create(null);
for (let i = 0, len = builtinExtensions.length; i < len; i++) {
resultMap[builtinExtensions[i].id] = builtinExtensions[i];
}
// Overwrite with extensions found in extra
for (let i = 0, len = extraBuiltinExtensions.length; i < len; i++) {
resultMap[extraBuiltinExtensions[i].id] = extraBuiltinExtensions[i];
}
let resultArr = Object.keys(resultMap).map((id) => resultMap[id]);
resultArr.sort((a, b) => {
const aLastSegment = path.basename(a.extensionLocation.fsPath);
const bLastSegment = path.basename(b.extensionLocation.fsPath);
if (aLastSegment < bLastSegment) {
return -1;
}
if (aLastSegment > bLastSegment) {
return 1;
}
return 0;
});
return resultArr;
});
}
const userExtensions = (
extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath
? TPromise.as([])
: 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
const developedExtensions = (
environmentService.isExtensionDevelopment
? ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionDevelopmentPath, false, true, translations), log
)
: TPromise.as([])
);
return TPromise.join([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++) {
@@ -954,78 +544,3 @@ export class ExtensionService extends Disposable implements IExtensionService {
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
}
function asLoggingProtocol(protocol: IMessagePassingProtocol): IMessagePassingProtocol {
protocol.onMessage(msg => {
console.log('%c[Extension \u2192 Window]%c[len: ' + strings.pad(msg.length, 5, ' ') + ']', 'color: darkgreen', 'color: grey', msg);
});
return {
onMessage: protocol.onMessage,
send(msg: any) {
protocol.send(msg);
console.log('%c[Window \u2192 Extension]%c[len: ' + strings.pad(msg.length, 5, ' ') + ']', 'color: darkgreen', 'color: grey', msg);
}
};
}
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 {
}
}

View File

@@ -0,0 +1,271 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { EnablementState, IExtensionEnablementService, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IURLHandler, IURLService } from 'vs/platform/url/common/url';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_SECONDS = 30 * 1000;
const URL_TO_HANDLE = 'extensionUrlHandler.urlToHandle';
function isExtensionId(value: string): boolean {
return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value);
}
export const IExtensionUrlHandler = createDecorator<IExtensionUrlHandler>('inactiveExtensionUrlHandler');
export interface IExtensionUrlHandler {
readonly _serviceBrand: any;
registerExtensionHandler(extensionId: string, handler: IURLHandler): void;
unregisterExtensionHandler(extensionId: string): void;
}
/**
* This class handles URLs which are directed towards inactive extensions.
* If a URL is directed towards an inactive extension, it buffers it,
* activates the extension and re-opens the URL once the extension registers
* a URL handler. If the extension never registers a URL handler, the urls
* will eventually be garbage collected.
*
* It also makes sure the user confirms opening URLs directed towards extensions.
*/
export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
readonly _serviceBrand: any;
private extensionHandlers = new Map<string, IURLHandler>();
private uriBuffer = new Map<string, { timestamp: number, uri: URI }[]>();
private disposable: IDisposable;
constructor(
@IURLService urlService: IURLService,
@IExtensionService private extensionService: IExtensionService,
@IDialogService private dialogService: IDialogService,
@INotificationService private notificationService: INotificationService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
@IWindowService private windowService: IWindowService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IStorageService private storageService: IStorageService
) {
const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS);
const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE);
if (urlToHandleValue) {
this.storageService.remove(URL_TO_HANDLE, StorageScope.WORKSPACE);
this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), true);
}
this.disposable = combinedDisposable([
urlService.registerHandler(this),
toDisposable(() => clearInterval(interval))
]);
}
handleURL(uri: URI, confirmed?: boolean): TPromise<boolean> {
if (!isExtensionId(uri.authority)) {
return TPromise.as(false);
}
const extensionId = uri.authority;
const wasHandlerAvailable = this.extensionHandlers.has(extensionId);
return this.extensionService.getExtension(extensionId).then(extension => {
if (!extension) {
return this.handleUnhandledURL(uri, { id: extensionId }).then(() => false);
}
const handleURL = () => {
const handler = this.extensionHandlers.get(extensionId);
if (handler) {
if (!wasHandlerAvailable) {
// forward it directly
return handler.handleURL(uri);
}
// let the ExtensionUrlHandler instance handle this
return TPromise.as(false);
}
// collect URI for eventual extension activation
const timestamp = new Date().getTime();
let uris = this.uriBuffer.get(extensionId);
if (!uris) {
uris = [];
this.uriBuffer.set(extensionId, uris);
}
uris.push({ timestamp, uri });
// activate the extension
return this.extensionService.activateByEvent(`onUri:${extensionId}`)
.then(() => true);
};
if (confirmed) {
return handleURL();
}
return this.dialogService.confirm({
message: localize('confirmUrl', "Allow an extension to open this URL?", extensionId),
detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('open', "&&Open"),
type: 'question'
}).then(result => {
if (!result.confirmed) {
return TPromise.as(true);
}
return handleURL();
});
});
}
registerExtensionHandler(extensionId: string, handler: IURLHandler): void {
this.extensionHandlers.set(extensionId, handler);
const uris = this.uriBuffer.get(extensionId) || [];
for (const { uri } of uris) {
handler.handleURL(uri);
}
this.uriBuffer.delete(extensionId);
}
unregisterExtensionHandler(extensionId: string): void {
this.extensionHandlers.delete(extensionId);
}
private async handleUnhandledURL(uri: URI, extensionIdentifier: IExtensionIdentifier): Promise<void> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const extension = installedExtensions.filter(e => areSameExtensions(e.galleryIdentifier, extensionIdentifier))[0];
// Extension is installed
if (extension) {
const enabled = this.extensionEnablementService.isEnabled(extension);
// Extension is not running. Reload the window to handle.
if (enabled) {
this.dialogService.confirm({
message: localize('reloadAndHandle', "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('reloadAndOpen', "&&Reload Window and Open"),
type: 'question'
}).then(result => {
if (result.confirmed) {
return this.reloadAndHandle(uri);
}
return null;
});
}
// Extension is disabled. Enable the extension and reload the window to handle.
else {
this.dialogService.confirm({
message: localize('enableAndHandle', "Extension '{0}' is disabled. Would you like to enable the extension and reload the window to open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('enableAndReload', "&&Enable and Open"),
type: 'question'
}).then((result): TPromise<void> | null => {
if (result.confirmed) {
return this.extensionEnablementService.setEnablement(extension, EnablementState.Enabled)
.then(() => this.reloadAndHandle(uri));
}
return null;
});
}
}
// Extension is not installed
else {
const galleryExtension = await this.galleryService.getExtension(extensionIdentifier);
if (galleryExtension) {
// Install the Extension and reload the window to handle.
this.dialogService.confirm({
message: localize('installAndHandle', "Extension '{0}' is not installed. Would you like to install the extension and reload the window to open this URL?", galleryExtension.displayName || galleryExtension.name),
detail: `${galleryExtension.displayName || galleryExtension.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
primaryButton: localize('install', "&&Install"),
type: 'question'
}).then(async result => {
if (result.confirmed) {
let notificationHandle: INotificationHandle | null = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) });
notificationHandle.progress.infinite();
notificationHandle.onDidClose(() => notificationHandle = null);
try {
await this.extensionManagementService.installFromGallery(galleryExtension);
const reloadMessage = localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString());
const reloadActionLabel = localize('Reload', "Reload Window and Open");
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateMessage(reloadMessage);
notificationHandle.updateActions({
primary: [new Action('reloadWindow', reloadActionLabel, undefined, true, () => this.reloadAndHandle(uri))]
});
} else {
this.notificationService.prompt(Severity.Info, reloadMessage,
[{
label: reloadActionLabel,
run: () => this.reloadAndHandle(uri)
}],
{ sticky: true }
);
}
} catch (e) {
if (notificationHandle) {
notificationHandle.progress.done();
notificationHandle.updateSeverity(Severity.Error);
notificationHandle.updateMessage(e);
} else {
this.notificationService.error(e);
}
}
}
});
}
}
}
private reloadAndHandle(url: URI): TPromise<void> {
this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE);
return this.windowService.reloadWindow();
}
// forget about all uris buffered more than 5 minutes ago
private garbageCollect(): void {
const now = new Date().getTime();
const uriBuffer = new Map<string, { timestamp: number, uri: URI }[]>();
this.uriBuffer.forEach((uris, extensionId) => {
uris = uris.filter(({ timestamp }) => now - timestamp < FIVE_MINUTES);
if (uris.length > 0) {
uriBuffer.set(extensionId, uris);
}
});
this.uriBuffer = uriBuffer;
}
dispose(): void {
this.disposable.dispose();
this.extensionHandlers.clear();
this.uriBuffer.clear();
}
}

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { EditorInput } from 'vs/workbench/common/editor';
export class RuntimeExtensionsInput extends EditorInput {
static readonly ID = 'workbench.runtimeExtensions.input';
constructor() {
super();
}
getTypeId(): string {
return RuntimeExtensionsInput.ID;
}
getName(): string {
return nls.localize('extensionsInputName', "Running Extensions");
}
matches(other: any): boolean {
if (!(other instanceof RuntimeExtensionsInput)) {
return false;
}
return true;
}
resolve(): Thenable<any> {
return Promise.resolve(null);
}
supportsSplitEditor(): boolean {
return false;
}
getResource(): URI {
return URI.from({
scheme: 'runtime-extensions',
path: 'default'
});
}
}