mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-11 18:48:33 -05:00
Merge VS Code 1.31.1 (#4283)
This commit is contained in:
@@ -16,7 +16,7 @@ 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 { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier } 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';
|
||||
@@ -50,6 +50,7 @@ export class CachedExtensionScanner {
|
||||
public readonly scannedExtensions: Promise<IExtensionDescription[]>;
|
||||
private _scannedExtensionsResolve: (result: IExtensionDescription[]) => void;
|
||||
private _scannedExtensionsReject: (err: any) => void;
|
||||
public readonly translationConfig: Promise<Translations>;
|
||||
|
||||
constructor(
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@@ -61,31 +62,58 @@ export class CachedExtensionScanner {
|
||||
this._scannedExtensionsResolve = resolve;
|
||||
this._scannedExtensionsReject = reject;
|
||||
});
|
||||
this.translationConfig = CachedExtensionScanner._readTranslationConfig();
|
||||
}
|
||||
|
||||
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);
|
||||
public async scanSingleExtension(path: string, isBuiltin: boolean, log: ILog): Promise<IExtensionDescription | null> {
|
||||
const translations = await this.translationConfig;
|
||||
|
||||
const version = pkg.version;
|
||||
const commit = product.commit;
|
||||
const devMode = !!process.env['VSCODE_DEV'];
|
||||
const locale = platform.locale;
|
||||
const input = new ExtensionScannerInput(version, commit, locale, devMode, path, isBuiltin, false, translations);
|
||||
return ExtensionScanner.scanSingleExtension(input, log);
|
||||
}
|
||||
|
||||
public async startScanningExtensions(log: ILog): Promise<void> {
|
||||
try {
|
||||
const translations = await this.translationConfig;
|
||||
const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations);
|
||||
|
||||
let result = new Map<string, IExtensionDescription>();
|
||||
system.forEach((systemExtension) => {
|
||||
const extensionKey = ExtensionIdentifier.toKey(systemExtension.identifier);
|
||||
const extension = result.get(extensionKey);
|
||||
if (extension) {
|
||||
log.warn(systemExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, systemExtension.extensionLocation.fsPath));
|
||||
}
|
||||
result.set(extensionKey, systemExtension);
|
||||
});
|
||||
user.forEach((userExtension) => {
|
||||
const extensionKey = ExtensionIdentifier.toKey(userExtension.identifier);
|
||||
const extension = result.get(extensionKey);
|
||||
if (extension) {
|
||||
log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, userExtension.extensionLocation.fsPath));
|
||||
}
|
||||
result.set(extensionKey, userExtension);
|
||||
});
|
||||
development.forEach(developedExtension => {
|
||||
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath));
|
||||
const extensionKey = ExtensionIdentifier.toKey(developedExtension.identifier);
|
||||
const extension = result.get(extensionKey);
|
||||
if (extension) {
|
||||
log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, developedExtension.extensionLocation.fsPath));
|
||||
}
|
||||
result.set(extensionKey, developedExtension);
|
||||
});
|
||||
let r: IExtensionDescription[] = [];
|
||||
result.forEach((value) => r.push(value));
|
||||
|
||||
this._scannedExtensionsResolve(r);
|
||||
} catch (err) {
|
||||
this._scannedExtensionsReject(err);
|
||||
}
|
||||
}
|
||||
|
||||
private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise<void> {
|
||||
@@ -198,86 +226,90 @@ export class CachedExtensionScanner {
|
||||
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);
|
||||
private static async _readTranslationConfig(): Promise<Translations> {
|
||||
if (platform.translationsConfigFile) {
|
||||
try {
|
||||
const content = await pfs.readFile(platform.translationsConfigFile, 'utf8');
|
||||
return JSON.parse(content) as Translations;
|
||||
} catch (err) {
|
||||
// no problemo
|
||||
}
|
||||
}
|
||||
return Object.create(null);
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
private static _scanInstalledExtensions(
|
||||
windowService: IWindowService,
|
||||
notificationService: INotificationService,
|
||||
environmentService: IEnvironmentService,
|
||||
extensionEnablementService: IExtensionEnablementService,
|
||||
log: ILog,
|
||||
translations: Translations
|
||||
): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// 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: [] };
|
||||
});
|
||||
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(undefined, err => {
|
||||
log.error('', err);
|
||||
return { system: [], user: [], development: [] };
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ 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 { Emitter, Event } 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';
|
||||
@@ -21,9 +20,8 @@ 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 { Protocol, generateRandomPipeName, BufferedProtocol } 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 { 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';
|
||||
@@ -34,15 +32,14 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
|
||||
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 { 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 { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export interface IExtensionHostStarter {
|
||||
readonly onCrashed: Event<[number, string]>;
|
||||
start(): Thenable<IMessagePassingProtocol>;
|
||||
start(): Promise<IMessagePassingProtocol>;
|
||||
getInspectPort(): number;
|
||||
dispose(): void;
|
||||
}
|
||||
@@ -103,7 +100,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
@IBroadcastService private readonly _broadcastService: IBroadcastService,
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@IWorkspaceConfigurationService private readonly _configurationService: IWorkspaceConfigurationService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@ICrashReporterService private readonly _crashReporterService: ICrashReporterService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@@ -176,7 +172,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
const opts = {
|
||||
env: objects.mixin(objects.deepClone(process.env), {
|
||||
AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess',
|
||||
AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
|
||||
PIPE_LOGGING: 'true',
|
||||
VERBOSE_LOGGING: true,
|
||||
VSCODE_IPC_HOOK_EXTHOST: pipeName,
|
||||
@@ -217,15 +213,15 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
type Output = { data: string, format: string[] };
|
||||
this._extensionHostProcess.stdout.setEncoding('utf8');
|
||||
this._extensionHostProcess.stderr.setEncoding('utf8');
|
||||
const onStdout = fromNodeEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
|
||||
const onStderr = fromNodeEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
|
||||
const onOutput = anyEvent(
|
||||
mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
|
||||
mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
|
||||
const onStdout = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
|
||||
const onStderr = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
|
||||
const onOutput = Event.any(
|
||||
Event.map(onStdout, o => ({ data: `%c${o}`, format: [''] })),
|
||||
Event.map(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
|
||||
);
|
||||
|
||||
// Debounce all output, so we can render it in the Chrome console as a group
|
||||
const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => {
|
||||
const onDebouncedOutput = Event.debounce<Output>(onOutput, (r, o) => {
|
||||
return r
|
||||
? { data: r.data + o.data, format: [...r.format, ...o.format] }
|
||||
: { data: o.data, format: o.format };
|
||||
@@ -356,7 +352,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
this._namedPipeServer.close();
|
||||
this._namedPipeServer = null;
|
||||
this._extensionHostConnection = socket;
|
||||
resolve(new Protocol(this._extensionHostConnection));
|
||||
|
||||
// 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 BufferedProtocol(new Protocol(this._extensionHostConnection)));
|
||||
});
|
||||
|
||||
}).then((protocol) => {
|
||||
@@ -402,10 +402,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
disposable.dispose();
|
||||
|
||||
// release this promise
|
||||
// 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));
|
||||
resolve(protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -420,15 +417,14 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
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,
|
||||
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined,
|
||||
appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : undefined,
|
||||
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
|
||||
extensionTestsPath: this._environmentService.extensionTestsPath,
|
||||
globalStorageHome: URI.file(this._environmentService.globalStorageHome)
|
||||
@@ -439,9 +435,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
id: workspace.id,
|
||||
name: this._labelService.getWorkspaceLabel(workspace)
|
||||
},
|
||||
resolvedExtensions: [],
|
||||
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,
|
||||
@@ -574,57 +569,3 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,27 @@ 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 { ProfileSession, IExtensionDescription } 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';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IUntitledResourceInput } from 'vs/workbench/common/editor';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
|
||||
// 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);
|
||||
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
|
||||
|
||||
export class ExtensionHostProcessManager extends Disposable {
|
||||
|
||||
@@ -42,7 +51,7 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
/**
|
||||
* 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; }>;
|
||||
private _extensionHostProcessProxy: Promise<{ value: ExtHostExtensionServiceShape; }>;
|
||||
|
||||
constructor(
|
||||
extensionHostProcessWorker: IExtensionHostStarter,
|
||||
@@ -70,6 +79,9 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
);
|
||||
this._extensionHostProcessProxy.then(() => {
|
||||
initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent));
|
||||
this._register(registerLatencyTestProvider({
|
||||
measure: () => this.measure()
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,12 +105,63 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// {{SQL CARBON EDIT}} - Add new getExtensionHostProcessWorker method
|
||||
public getExtenstionHostProcessWorker(): IExtensionHostStarter {
|
||||
return this._extensionHostProcessWorker;
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
private async measure(): Promise<ExtHostLatencyResult> {
|
||||
const latency = await this._measureLatency();
|
||||
const down = await this._measureDown();
|
||||
const up = await this._measureUp();
|
||||
return {
|
||||
remoteAuthority: this._remoteAuthority,
|
||||
latency,
|
||||
down,
|
||||
up
|
||||
};
|
||||
}
|
||||
|
||||
private async _measureLatency(): Promise<number> {
|
||||
const COUNT = 10;
|
||||
|
||||
const { value: proxy } = await this._extensionHostProcessProxy;
|
||||
let sum = 0;
|
||||
for (let i = 0; i < COUNT; i++) {
|
||||
const sw = StopWatch.create(true);
|
||||
await proxy.$test_latency(i);
|
||||
sw.stop();
|
||||
sum += sw.elapsed();
|
||||
}
|
||||
return (sum / COUNT);
|
||||
}
|
||||
|
||||
private static _convert(byteCount: number, elapsedMillis: number): number {
|
||||
return (byteCount * 1000 * 8) / elapsedMillis;
|
||||
}
|
||||
|
||||
private async _measureUp(): Promise<number> {
|
||||
const SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
const { value: proxy } = await this._extensionHostProcessProxy;
|
||||
let b = Buffer.alloc(SIZE, Math.random() % 256);
|
||||
const sw = StopWatch.create(true);
|
||||
await proxy.$test_up(b);
|
||||
sw.stop();
|
||||
return ExtensionHostProcessManager._convert(SIZE, sw.elapsed());
|
||||
}
|
||||
|
||||
private async _measureDown(): Promise<number> {
|
||||
const SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
const { value: proxy } = await this._extensionHostProcessProxy;
|
||||
const sw = StopWatch.create(true);
|
||||
await proxy.$test_down(SIZE);
|
||||
sw.stop();
|
||||
return ExtensionHostProcessManager._convert(SIZE, sw.elapsed());
|
||||
}
|
||||
|
||||
public canProfileExtensionHost(): boolean {
|
||||
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
|
||||
}
|
||||
@@ -130,8 +193,7 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
|
||||
// Customers
|
||||
const customers = ExtHostCustomersRegistry.getCustomers();
|
||||
for (let i = 0, len = customers.length; i < len; i++) {
|
||||
const ctor = customers[i];
|
||||
for (const ctor of customers) {
|
||||
const instance = this._instantiationService.createInstance(ctor, extHostContext);
|
||||
this._extensionHostProcessCustomers.push(instance);
|
||||
}
|
||||
@@ -143,7 +205,13 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);
|
||||
}
|
||||
|
||||
public activateByEvent(activationEvent: string): Thenable<void> {
|
||||
public activate(extension: ExtensionIdentifier, activationEvent: string): Promise<void> {
|
||||
return this._extensionHostProcessProxy.then((proxy) => {
|
||||
return proxy.value.$activate(extension, activationEvent);
|
||||
});
|
||||
}
|
||||
|
||||
public activateByEvent(activationEvent: string): Promise<void> {
|
||||
if (this._extensionHostProcessFinishedActivateEvents[activationEvent] || !this._extensionHostProcessProxy) {
|
||||
return NO_OP_VOID_PROMISE;
|
||||
}
|
||||
@@ -179,13 +247,28 @@ export class ExtensionHostProcessManager extends Disposable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public resolveAuthority(remoteAuthority: string): Thenable<ResolvedAuthority> {
|
||||
public resolveAuthority(remoteAuthority: string): Promise<ResolvedAuthority> {
|
||||
const authorityPlusIndex = remoteAuthority.indexOf('+');
|
||||
if (authorityPlusIndex === -1) {
|
||||
// This authority does not need to be resolved, simply parse the port number
|
||||
const pieces = remoteAuthority.split(':');
|
||||
return Promise.resolve({
|
||||
authority: remoteAuthority,
|
||||
host: pieces[0],
|
||||
port: parseInt(pieces[1], 10),
|
||||
syncExtensions: false
|
||||
});
|
||||
}
|
||||
return this._extensionHostProcessProxy.then(proxy => proxy.value.$resolveAuthority(remoteAuthority));
|
||||
}
|
||||
|
||||
public start(enabledExtensionIds: string[]): Thenable<void> {
|
||||
public start(enabledExtensionIds: ExtensionIdentifier[]): Promise<void> {
|
||||
return this._extensionHostProcessProxy.then(proxy => proxy.value.$startExtensionHost(enabledExtensionIds));
|
||||
}
|
||||
|
||||
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
|
||||
return this._extensionHostProcessProxy.then(proxy => proxy.value.$deltaExtensions(toAdd, toRemove));
|
||||
}
|
||||
}
|
||||
|
||||
const colorTables = [
|
||||
@@ -230,7 +313,7 @@ class RPCLogger implements IRPCProtocolLogger {
|
||||
} else {
|
||||
args.push(data);
|
||||
}
|
||||
console.log.apply(console, args);
|
||||
console.log.apply(console, args as [string, ...string[]]);
|
||||
}
|
||||
|
||||
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void {
|
||||
@@ -243,3 +326,68 @@ class RPCLogger implements IRPCProtocolLogger {
|
||||
this._log('Win \u2192 Ext', this._totalOutgoing, msgLength, req, initiator, str, data);
|
||||
}
|
||||
}
|
||||
|
||||
interface ExtHostLatencyResult {
|
||||
remoteAuthority: string;
|
||||
up: number;
|
||||
down: number;
|
||||
latency: number;
|
||||
}
|
||||
|
||||
interface ExtHostLatencyProvider {
|
||||
measure(): Promise<ExtHostLatencyResult>;
|
||||
}
|
||||
|
||||
let providers: ExtHostLatencyProvider[] = [];
|
||||
function registerLatencyTestProvider(provider: ExtHostLatencyProvider): IDisposable {
|
||||
providers.push(provider);
|
||||
return {
|
||||
dispose: () => {
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
if (providers[i] === provider) {
|
||||
providers.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getLatencyTestProviders(): ExtHostLatencyProvider[] {
|
||||
return providers.slice(0);
|
||||
}
|
||||
|
||||
export class MeasureExtHostLatencyAction extends Action {
|
||||
public static readonly ID = 'editor.action.measureExtHostLatency';
|
||||
public static readonly LABEL = nls.localize('measureExtHostLatency', "Measure Extension Host Latency");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorService private readonly _editorService: IEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public async run(): Promise<any> {
|
||||
const measurements = await Promise.all(getLatencyTestProviders().map(provider => provider.measure()));
|
||||
this._editorService.openEditor({ contents: measurements.map(MeasureExtHostLatencyAction._print).join('\n\n'), options: { pinned: true } } as IUntitledResourceInput);
|
||||
}
|
||||
|
||||
private static _print(m: ExtHostLatencyResult): string {
|
||||
return `${m.remoteAuthority ? `Authority: ${m.remoteAuthority}\n` : ``}Roundtrip latency: ${m.latency.toFixed(3)}ms\nUp: ${MeasureExtHostLatencyAction._printSpeed(m.up)}\nDown: ${MeasureExtHostLatencyAction._printSpeed(m.down)}\n`;
|
||||
}
|
||||
|
||||
private static _printSpeed(n: number): string {
|
||||
if (n <= 1024) {
|
||||
return `${n} bps`;
|
||||
}
|
||||
if (n < 1024 * 1024) {
|
||||
return `${(n / 1024).toFixed(1)} kbps`;
|
||||
}
|
||||
return `${(n / 1024 / 1024).toFixed(1)} Mbps`;
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MeasureExtHostLatencyAction, MeasureExtHostLatencyAction.ID, MeasureExtHostLatencyAction.LABEL), 'Developer: Measure Extension Host Latency', nls.localize('developer', "Developer"));
|
||||
|
||||
@@ -15,7 +15,7 @@ export class ExtensionHostProfiler {
|
||||
|
||||
public async start(): Promise<ProfileSession> {
|
||||
const profiler = await import('v8-inspect-profiler');
|
||||
const session = await profiler.startProfiling({ port: this._port });
|
||||
const session = await profiler.startProfiling({ port: this._port, checkForPaused: true });
|
||||
return {
|
||||
stop: async () => {
|
||||
const profile = await session.stop();
|
||||
@@ -56,26 +56,29 @@ export class ExtensionHostProfiler {
|
||||
} else if (segmentId === 'self' && node.callFrame.url) {
|
||||
let extension = searchTree.findSubstr(node.callFrame.url);
|
||||
if (extension) {
|
||||
segmentId = extension.id;
|
||||
segmentId = extension.identifier.value;
|
||||
}
|
||||
}
|
||||
idsToSegmentId.set(node.id, segmentId);
|
||||
|
||||
if (node.children) {
|
||||
for (let child of node.children) {
|
||||
visit(idsToNodes.get(child), segmentId);
|
||||
for (const child of node.children) {
|
||||
const childNode = idsToNodes.get(child);
|
||||
if (childNode) {
|
||||
visit(childNode, segmentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(nodes[0], null);
|
||||
|
||||
let samples = profile.samples;
|
||||
let timeDeltas = profile.timeDeltas;
|
||||
const samples = profile.samples || [];
|
||||
let timeDeltas = profile.timeDeltas || [];
|
||||
let distilledDeltas: number[] = [];
|
||||
let distilledIds: ProfileSegmentId[] = [];
|
||||
|
||||
let currSegmentTime = 0;
|
||||
let currSegmentId: string = void 0;
|
||||
let currSegmentId: string | undefined;
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
let id = samples[i];
|
||||
let segmentId = idsToSegmentId.get(id);
|
||||
@@ -84,7 +87,7 @@ export class ExtensionHostProfiler {
|
||||
distilledIds.push(currSegmentId);
|
||||
distilledDeltas.push(currSegmentTime);
|
||||
}
|
||||
currSegmentId = segmentId;
|
||||
currSegmentId = segmentId || undefined;
|
||||
currSegmentTime = 0;
|
||||
}
|
||||
currSegmentTime += timeDeltas[i];
|
||||
@@ -93,9 +96,6 @@ export class ExtensionHostProfiler {
|
||||
distilledIds.push(currSegmentId);
|
||||
distilledDeltas.push(currSegmentTime);
|
||||
}
|
||||
idsToNodes = null;
|
||||
idsToSegmentId = null;
|
||||
searchTree = null;
|
||||
|
||||
return {
|
||||
startTime: profile.startTime,
|
||||
|
||||
@@ -13,8 +13,8 @@ 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 { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { BetterMergeId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
@@ -22,19 +22,40 @@ 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 { ActivationTimes, ExtensionPointContribution, IExtensionDescription, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ActivationTimes, ExtensionPointContribution, IExtensionDescription, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } 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';
|
||||
import { ExtensionIdentifier, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const NO_OP_VOID_PROMISE = Promise.resolve<void>(void 0);
|
||||
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
|
||||
|
||||
schema.properties.engines.properties.vscode.default = `^${pkg.version}`;
|
||||
|
||||
let productAllowProposedApi: Set<string> = null;
|
||||
function allowProposedApiFromProduct(id: ExtensionIdentifier): boolean {
|
||||
// create set if needed
|
||||
if (productAllowProposedApi === null) {
|
||||
productAllowProposedApi = new Set<string>();
|
||||
if (isNonEmptyArray(product.extensionAllowedProposedApi)) {
|
||||
product.extensionAllowedProposedApi.forEach((id) => productAllowProposedApi.add(ExtensionIdentifier.toKey(id)));
|
||||
}
|
||||
}
|
||||
return productAllowProposedApi.has(ExtensionIdentifier.toKey(id));
|
||||
}
|
||||
|
||||
class DeltaExtensionsQueueItem {
|
||||
constructor(
|
||||
public readonly toAdd: IExtension[],
|
||||
public readonly toRemove: string[]
|
||||
) { }
|
||||
}
|
||||
|
||||
export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
@@ -43,15 +64,19 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
private _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _installedExtensionsReady: Barrier;
|
||||
private readonly _isDev: boolean;
|
||||
private readonly _extensionsMessages: { [id: string]: IMessage[] };
|
||||
private readonly _extensionsMessages: Map<string, IMessage[]>;
|
||||
private _allRequestedActivateEvents: { [activationEvent: string]: boolean; };
|
||||
private readonly _extensionScanner: CachedExtensionScanner;
|
||||
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
|
||||
|
||||
private readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>({ leakWarningThreshold: 500 }));
|
||||
private readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>());
|
||||
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 _onDidChangeExtensionsStatus: Emitter<ExtensionIdentifier[]> = this._register(new Emitter<ExtensionIdentifier[]>());
|
||||
public readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = this._onDidChangeExtensionsStatus.event;
|
||||
|
||||
private readonly _onDidChangeExtensions: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeExtensions: Event<void> = this._onDidChangeExtensions.event;
|
||||
|
||||
private readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
|
||||
public readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.event;
|
||||
@@ -61,8 +86,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
// --- Members used per extension host process
|
||||
private _extensionHostProcessManagers: ExtensionHostProcessManager[];
|
||||
private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; };
|
||||
private _extensionHostExtensionRuntimeErrors: { [id: string]: Error[]; };
|
||||
private _extensionHostActiveExtensions: Map<string, ExtensionIdentifier>;
|
||||
private _extensionHostProcessActivationTimes: Map<string, ActivationTimes>;
|
||||
private _extensionHostExtensionRuntimeErrors: Map<string, Error[]>;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@@ -70,22 +96,24 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
|
||||
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
|
||||
@IWindowService private readonly _windowService: IWindowService,
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
|
||||
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService
|
||||
) {
|
||||
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._extensionsMessages = new Map<string, IMessage[]>();
|
||||
this._allRequestedActivateEvents = Object.create(null);
|
||||
this._extensionScanner = this._instantiationService.createInstance(CachedExtensionScanner);
|
||||
this._deltaExtensionsQueue = [];
|
||||
|
||||
this._extensionHostProcessManagers = [];
|
||||
this._extensionHostProcessActivationTimes = Object.create(null);
|
||||
this._extensionHostExtensionRuntimeErrors = Object.create(null);
|
||||
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
|
||||
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
|
||||
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
|
||||
|
||||
this._startDelayed(this._lifecycleService);
|
||||
|
||||
@@ -97,6 +125,255 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
this._extensionEnablementService.onEnablementChanged((extensions) => {
|
||||
let toAdd: IExtension[] = [];
|
||||
let toRemove: string[] = [];
|
||||
for (const extension of extensions) {
|
||||
if (this._extensionEnablementService.isEnabled(extension)) {
|
||||
// an extension has been enabled
|
||||
toAdd.push(extension);
|
||||
} else {
|
||||
// an extension has been disabled
|
||||
toRemove.push(extension.identifier.id);
|
||||
}
|
||||
}
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
|
||||
});
|
||||
|
||||
this._extensionManagementService.onDidInstallExtension((event) => {
|
||||
if (event.local) {
|
||||
if (this._extensionEnablementService.isEnabled(event.local)) {
|
||||
// an extension has been installed
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([event.local], []));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._extensionManagementService.onDidUninstallExtension((event) => {
|
||||
if (!event.error) {
|
||||
// an extension has been uninstalled
|
||||
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _inHandleDeltaExtensions = false;
|
||||
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
|
||||
this._deltaExtensionsQueue.push(item);
|
||||
if (this._inHandleDeltaExtensions) {
|
||||
// Let the current item finish, the new one will be picked up
|
||||
return;
|
||||
}
|
||||
|
||||
while (this._deltaExtensionsQueue.length > 0) {
|
||||
const item = this._deltaExtensionsQueue.shift();
|
||||
try {
|
||||
this._inHandleDeltaExtensions = true;
|
||||
await this._deltaExtensions(item.toAdd, item.toRemove);
|
||||
} finally {
|
||||
this._inHandleDeltaExtensions = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
|
||||
if (this._windowService.getConfiguration().remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
|
||||
let toAdd: IExtensionDescription[] = [];
|
||||
for (let i = 0, len = _toAdd.length; i < len; i++) {
|
||||
const extension = _toAdd[i];
|
||||
|
||||
if (extension.location.scheme !== Schemas.file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingExtensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
|
||||
if (existingExtensionDescription) {
|
||||
// this extension is already running (most likely at a different version)
|
||||
continue;
|
||||
}
|
||||
|
||||
const extensionDescription = await this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System, this.createLogger());
|
||||
if (!extensionDescription || !this._usesOnlyDynamicExtensionPoints(extensionDescription)) {
|
||||
// uses non-dynamic extension point
|
||||
continue;
|
||||
}
|
||||
|
||||
toAdd.push(extensionDescription);
|
||||
}
|
||||
|
||||
let toRemove: IExtensionDescription[] = [];
|
||||
for (let i = 0, len = _toRemove.length; i < len; i++) {
|
||||
const extensionId = _toRemove[i];
|
||||
const extensionDescription = this._registry.getExtensionDescription(extensionId);
|
||||
if (!extensionDescription) {
|
||||
// ignore disabling/uninstalling an extension which is not running
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this._canRemoveExtension(extensionDescription)) {
|
||||
// uses non-dynamic extension point or is activated
|
||||
continue;
|
||||
}
|
||||
|
||||
toRemove.push(extensionDescription);
|
||||
}
|
||||
|
||||
if (toAdd.length === 0 && toRemove.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the local registry
|
||||
this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
|
||||
|
||||
// Update extension points
|
||||
this._rehandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
|
||||
|
||||
// Update the extension host
|
||||
if (this._extensionHostProcessManagers.length > 0) {
|
||||
await this._extensionHostProcessManagers[0].deltaExtensions(toAdd, toRemove.map(e => e.identifier));
|
||||
}
|
||||
|
||||
this._onDidChangeExtensions.fire(undefined);
|
||||
|
||||
for (let i = 0; i < toAdd.length; i++) {
|
||||
this._activateAddedExtensionIfNeeded(toAdd[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private _rehandleExtensionPoints(extensionDescriptions: IExtensionDescription[]): void {
|
||||
const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null);
|
||||
for (let extensionDescription of extensionDescriptions) {
|
||||
if (extensionDescription.contributes) {
|
||||
for (let extPointName in extensionDescription.contributes) {
|
||||
if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) {
|
||||
affectedExtensionPoints[extPointName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
|
||||
|
||||
const availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
|
||||
for (let i = 0, len = extensionPoints.length; i < len; i++) {
|
||||
if (affectedExtensionPoints[extensionPoints[i].name]) {
|
||||
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _usesOnlyDynamicExtensionPoints(extension: IExtensionDescription): boolean {
|
||||
const extensionPoints = ExtensionsRegistry.getExtensionPointsMap();
|
||||
if (extension.contributes) {
|
||||
for (let extPointName in extension.contributes) {
|
||||
if (hasOwnProperty.call(extension.contributes, extPointName)) {
|
||||
const extPoint = extensionPoints[extPointName];
|
||||
if (extPoint) {
|
||||
if (!extPoint.isDynamic) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// This extension has a 3rd party (unknown) extension point
|
||||
// ===> require a reload for now...
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public canAddExtension(extension: IExtensionDescription): boolean {
|
||||
if (this._windowService.getConfiguration().remoteAuthority) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extension.extensionLocation.scheme !== Schemas.file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
|
||||
if (extensionDescription) {
|
||||
// ignore adding an extension which is already running and cannot be removed
|
||||
if (!this._canRemoveExtension(extensionDescription)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return this._usesOnlyDynamicExtensionPoints(extension);
|
||||
}
|
||||
|
||||
public canRemoveExtension(extension: IExtensionDescription): boolean {
|
||||
if (this._windowService.getConfiguration().remoteAuthority) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extension.extensionLocation.scheme !== Schemas.file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
|
||||
if (!extensionDescription) {
|
||||
// ignore removing an extension which is not running
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._canRemoveExtension(extensionDescription);
|
||||
}
|
||||
|
||||
private _canRemoveExtension(extension: IExtensionDescription): boolean {
|
||||
if (this._extensionHostActiveExtensions.has(ExtensionIdentifier.toKey(extension.identifier))) {
|
||||
// Extension is running, cannot remove it safely
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._usesOnlyDynamicExtensionPoints(extension);
|
||||
}
|
||||
|
||||
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
|
||||
|
||||
let shouldActivate = false;
|
||||
let shouldActivateReason: string | null = null;
|
||||
if (Array.isArray(extensionDescription.activationEvents)) {
|
||||
for (let activationEvent of extensionDescription.activationEvents) {
|
||||
// TODO@joao: there's no easy way to contribute this
|
||||
if (activationEvent === 'onUri') {
|
||||
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
|
||||
}
|
||||
|
||||
if (this._allRequestedActivateEvents[activationEvent]) {
|
||||
// This activation event was fired before the extension was added
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
|
||||
if (activationEvent === '*') {
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
|
||||
if (/^workspaceContains/.test(activationEvent)) {
|
||||
// do not trigger a search, just activate in this case...
|
||||
shouldActivate = true;
|
||||
shouldActivateReason = activationEvent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldActivate) {
|
||||
await Promise.all(
|
||||
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, shouldActivateReason))
|
||||
).then(() => { });
|
||||
}
|
||||
}
|
||||
|
||||
private _startDelayed(lifecycleService: ILifecycleService): void {
|
||||
@@ -137,14 +414,18 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
|
||||
private _stopExtensionHostProcess(): void {
|
||||
const previouslyActivatedExtensionIds = Object.keys(this._extensionHostProcessActivationTimes);
|
||||
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
|
||||
this._extensionHostActiveExtensions.forEach((value) => {
|
||||
previouslyActivatedExtensionIds.push(value);
|
||||
});
|
||||
|
||||
for (let i = 0; i < this._extensionHostProcessManagers.length; i++) {
|
||||
this._extensionHostProcessManagers[i].dispose();
|
||||
for (const manager of this._extensionHostProcessManagers) {
|
||||
manager.dispose();
|
||||
}
|
||||
this._extensionHostProcessManagers = [];
|
||||
this._extensionHostProcessActivationTimes = Object.create(null);
|
||||
this._extensionHostExtensionRuntimeErrors = Object.create(null);
|
||||
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
|
||||
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
|
||||
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
|
||||
|
||||
if (previouslyActivatedExtensionIds.length > 0) {
|
||||
this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds);
|
||||
@@ -154,7 +435,18 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
|
||||
this._stopExtensionHostProcess();
|
||||
|
||||
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, !isInitialStart, this.getExtensions(), this._extensionHostLogsLocation);
|
||||
let autoStart: boolean;
|
||||
let extensions: Promise<IExtensionDescription[]>;
|
||||
if (isInitialStart) {
|
||||
autoStart = false;
|
||||
extensions = this._extensionScanner.scannedExtensions;
|
||||
} else {
|
||||
// restart case
|
||||
autoStart = true;
|
||||
extensions = this.getExtensions();
|
||||
}
|
||||
|
||||
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, 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 }); });
|
||||
@@ -205,14 +497,14 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
if (this._installedExtensionsReady.isOpen()) {
|
||||
// Extensions have been scanned and interpreted
|
||||
|
||||
// Record the fact that this activationEvent was requested (in case of a restart)
|
||||
this._allRequestedActivateEvents[activationEvent] = true;
|
||||
|
||||
if (!this._registry.containsActivationEvent(activationEvent)) {
|
||||
// There is no extension that is interested in this activation event
|
||||
return NO_OP_VOID_PROMISE;
|
||||
}
|
||||
|
||||
// Record the fact that this activationEvent was requested (in case of a restart)
|
||||
this._allRequestedActivateEvents[activationEvent] = true;
|
||||
|
||||
return this._activateByEvent(activationEvent);
|
||||
} else {
|
||||
// Extensions have not been scanned yet.
|
||||
@@ -272,13 +564,12 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
let result: { [id: string]: IExtensionsStatus; } = Object.create(null);
|
||||
if (this._registry) {
|
||||
const extensions = this._registry.getAllExtensionDescriptions();
|
||||
for (let i = 0, len = extensions.length; i < len; i++) {
|
||||
const extension = extensions[i];
|
||||
const id = extension.id;
|
||||
result[id] = {
|
||||
messages: this._extensionsMessages[id],
|
||||
activationTimes: this._extensionHostProcessActivationTimes[id],
|
||||
runtimeErrors: this._extensionHostExtensionRuntimeErrors[id],
|
||||
for (const extension of extensions) {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extension.identifier);
|
||||
result[extension.identifier.value] = {
|
||||
messages: this._extensionsMessages.get(extensionKey),
|
||||
activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey),
|
||||
runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -316,23 +607,29 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
// --- impl
|
||||
|
||||
private async _scanAndHandleExtensions(): Promise<void> {
|
||||
this._extensionScanner.startScanningExtensions(new Logger((severity, source, message) => {
|
||||
private createLogger(): Logger {
|
||||
return new Logger((severity, source, message) => {
|
||||
if (this._isDev && source) {
|
||||
this._logOrShowMessage(severity, `[${source}]: ${message}`);
|
||||
} else {
|
||||
this._logOrShowMessage(severity, message);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private async _scanAndHandleExtensions(): Promise<void> {
|
||||
this._extensionScanner.startScanningExtensions(this.createLogger());
|
||||
|
||||
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);
|
||||
|
||||
this._handleExtensionPoints(enabledExtensions);
|
||||
extensionHost.start(enabledExtensions.map(extension => extension.identifier));
|
||||
this._releaseBarrier();
|
||||
}
|
||||
|
||||
private _onHasExtensions(allExtensions: IExtensionDescription[]): void {
|
||||
private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void {
|
||||
this._registry = new ExtensionDescriptionRegistry(allExtensions);
|
||||
|
||||
let availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
@@ -343,58 +640,65 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
for (let i = 0, len = extensionPoints.length; i < len; i++) {
|
||||
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private _releaseBarrier(): void {
|
||||
perf.mark('extensionHostReady');
|
||||
this._installedExtensionsReady.open();
|
||||
this._onDidRegisterExtensions.fire(void 0);
|
||||
this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id));
|
||||
this._onDidRegisterExtensions.fire(undefined);
|
||||
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
|
||||
}
|
||||
|
||||
private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise<IExtensionDescription[]> {
|
||||
return this._extensionEnablementService.getDisabledExtensions()
|
||||
.then(disabledExtensions => {
|
||||
|
||||
const result: { [extensionId: string]: IExtensionDescription; } = {};
|
||||
const extensionsToDisable: IExtensionIdentifier[] = [];
|
||||
const runtimeExtensions: IExtensionDescription[] = [];
|
||||
const extensionsToDisable: IExtensionDescription[] = [];
|
||||
const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }];
|
||||
|
||||
const enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || [];
|
||||
let 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)) {
|
||||
if (!allExtensions.some(description => ExtensionIdentifier.equals(description.identifier, id))) {
|
||||
console.error(notFound(id));
|
||||
}
|
||||
});
|
||||
// Make enabled proposed API be lowercase for case insensitive comparison
|
||||
if (Array.isArray(enableProposedApiFor)) {
|
||||
enableProposedApiFor = enableProposedApiFor.map(id => id.toLowerCase());
|
||||
} else {
|
||||
enableProposedApiFor = enableProposedApiFor.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
const enableProposedApiForAll = !this._environmentService.isBuilt ||
|
||||
(!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong.indexOf('Insiders') >= 0) ||
|
||||
(!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') ||
|
||||
(enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args);
|
||||
|
||||
for (const extension of allExtensions) {
|
||||
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))) {
|
||||
if (disabledExtensions.some(disabled => areSameExtensions(disabled, { id: extension.identifier.value }))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!extension.isBuiltin) {
|
||||
// Check if the extension is changed to system extension
|
||||
const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.id }))[0];
|
||||
const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0];
|
||||
if (userMigratedSystemExtension) {
|
||||
extensionsToDisable.push(userMigratedSystemExtension);
|
||||
extensionsToDisable.push(extension);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result[extension.id] = this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor);
|
||||
runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor));
|
||||
}
|
||||
const runtimeExtensions = Object.keys(result).map(name => result[name]);
|
||||
|
||||
this._telemetryService.publicLog('extensionsScanned', {
|
||||
totalCount: runtimeExtensions.length,
|
||||
@@ -402,14 +706,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
});
|
||||
|
||||
if (extensionsToDisable.length) {
|
||||
return this._extensionManagementService.getInstalled(LocalExtensionType.User)
|
||||
.then(installed => {
|
||||
const toDisable = installed.filter(i => extensionsToDisable.some(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i) }, e)));
|
||||
return Promise.all(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled)));
|
||||
})
|
||||
.then(() => {
|
||||
return runtimeExtensions;
|
||||
});
|
||||
return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled)
|
||||
.then(() => runtimeExtensions);
|
||||
} else {
|
||||
return runtimeExtensions;
|
||||
}
|
||||
@@ -417,9 +715,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
|
||||
private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription {
|
||||
if (isNonEmptyArray(product.extensionAllowedProposedApi)
|
||||
&& product.extensionAllowedProposedApi.indexOf(extension.id) >= 0
|
||||
) {
|
||||
if (allowProposedApiFromProduct(extension.identifier)) {
|
||||
// fast lane -> proposed api is available to all extensions
|
||||
// that are listed in product.json-files
|
||||
extension.enableProposedApi = true;
|
||||
@@ -427,29 +723,30 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
} else if (extension.enableProposedApi && !extension.isBuiltin) {
|
||||
if (
|
||||
!enableProposedApiForAll &&
|
||||
enableProposedApiFor.indexOf(extension.id) < 0
|
||||
enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0
|
||||
) {
|
||||
extension.enableProposedApi = false;
|
||||
console.error(`Extension '${extension.id} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
|
||||
console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
|
||||
|
||||
} else {
|
||||
// proposed api is available when developing or when an extension was explicitly
|
||||
// spelled out via a command line argument
|
||||
console.warn(`Extension '${extension.id}' uses PROPOSED API which is subject to change and removal without notice.`);
|
||||
console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`);
|
||||
}
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
private _handleExtensionPointMessage(msg: IMessage) {
|
||||
const extensionKey = ExtensionIdentifier.toKey(msg.extensionId);
|
||||
|
||||
if (!this._extensionsMessages[msg.extensionId]) {
|
||||
this._extensionsMessages[msg.extensionId] = [];
|
||||
if (!this._extensionsMessages.has(extensionKey)) {
|
||||
this._extensionsMessages.set(extensionKey, []);
|
||||
}
|
||||
this._extensionsMessages[msg.extensionId].push(msg);
|
||||
this._extensionsMessages.get(extensionKey).push(msg);
|
||||
|
||||
const extension = this._registry.getExtensionDescription(msg.extensionId);
|
||||
const strMsg = `[${msg.extensionId}]: ${msg.message}`;
|
||||
const strMsg = `[${msg.extensionId.value}]: ${msg.message}`;
|
||||
if (extension && extension.isUnderDevelopment) {
|
||||
// This message is about the extension currently being developed
|
||||
this._showMessageToUser(msg.type, strMsg);
|
||||
@@ -468,7 +765,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('extensionsMessage', {
|
||||
type, extensionId, extensionPointId, message
|
||||
type, extensionId: extensionId.value, extensionPointId, message
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -518,24 +815,30 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
}
|
||||
|
||||
public _onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
|
||||
this._extensionHostProcessActivationTimes[extensionId] = new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent);
|
||||
public _onWillActivateExtension(extensionId: ExtensionIdentifier): void {
|
||||
this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId);
|
||||
}
|
||||
|
||||
public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
|
||||
this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent));
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _onExtensionRuntimeError(extensionId: string, err: Error): void {
|
||||
if (!this._extensionHostExtensionRuntimeErrors[extensionId]) {
|
||||
this._extensionHostExtensionRuntimeErrors[extensionId] = [];
|
||||
public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extensionId);
|
||||
if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) {
|
||||
this._extensionHostExtensionRuntimeErrors.set(extensionKey, []);
|
||||
}
|
||||
this._extensionHostExtensionRuntimeErrors[extensionId].push(err);
|
||||
this._extensionHostExtensionRuntimeErrors.get(extensionKey).push(err);
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _addMessage(extensionId: string, severity: Severity, message: string): void {
|
||||
if (!this._extensionsMessages[extensionId]) {
|
||||
this._extensionsMessages[extensionId] = [];
|
||||
public _addMessage(extensionId: ExtensionIdentifier, severity: Severity, message: string): void {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extensionId);
|
||||
if (!this._extensionsMessages.has(extensionKey)) {
|
||||
this._extensionsMessages.set(extensionKey, []);
|
||||
}
|
||||
this._extensionsMessages[extensionId].push({
|
||||
this._extensionsMessages.get(extensionKey).push({
|
||||
type: severity,
|
||||
message: message,
|
||||
extensionId: null,
|
||||
|
||||
@@ -7,7 +7,6 @@ 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';
|
||||
@@ -17,6 +16,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
|
||||
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';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||
const THIRTY_SECONDS = 30 * 1000;
|
||||
@@ -30,8 +30,8 @@ export const IExtensionUrlHandler = createDecorator<IExtensionUrlHandler>('inact
|
||||
|
||||
export interface IExtensionUrlHandler {
|
||||
readonly _serviceBrand: any;
|
||||
registerExtensionHandler(extensionId: string, handler: IURLHandler): void;
|
||||
unregisterExtensionHandler(extensionId: string): void;
|
||||
registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void;
|
||||
unregisterExtensionHandler(extensionId: ExtensionIdentifier): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,14 +53,14 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||
|
||||
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
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
) {
|
||||
const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS);
|
||||
const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE);
|
||||
@@ -75,87 +75,80 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||
]);
|
||||
}
|
||||
|
||||
handleURL(uri: URI, confirmed?: boolean): TPromise<boolean> {
|
||||
async handleURL(uri: URI, confirmed?: boolean): Promise<boolean> {
|
||||
if (!isExtensionId(uri.authority)) {
|
||||
return TPromise.as(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
const extensionId = uri.authority;
|
||||
const wasHandlerAvailable = this.extensionHandlers.has(extensionId);
|
||||
const wasHandlerAvailable = this.extensionHandlers.has(ExtensionIdentifier.toKey(extensionId));
|
||||
const extension = await this.extensionService.getExtension(extensionId);
|
||||
|
||||
return this.extensionService.getExtension(extensionId).then(extension => {
|
||||
if (!extension) {
|
||||
await this.handleUnhandledURL(uri, { id: extensionId });
|
||||
return true;
|
||||
}
|
||||
|
||||
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({
|
||||
if (!confirmed) {
|
||||
const result = await 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();
|
||||
});
|
||||
});
|
||||
|
||||
if (!result.confirmed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const handler = this.extensionHandlers.get(ExtensionIdentifier.toKey(extensionId));
|
||||
|
||||
if (handler) {
|
||||
if (!wasHandlerAvailable) {
|
||||
// forward it directly
|
||||
return await handler.handleURL(uri);
|
||||
}
|
||||
|
||||
// let the ExtensionUrlHandler instance handle this
|
||||
return false;
|
||||
}
|
||||
|
||||
// collect URI for eventual extension activation
|
||||
const timestamp = new Date().getTime();
|
||||
let uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId));
|
||||
|
||||
if (!uris) {
|
||||
uris = [];
|
||||
this.uriBuffer.set(ExtensionIdentifier.toKey(extensionId), uris);
|
||||
}
|
||||
|
||||
uris.push({ timestamp, uri });
|
||||
|
||||
// activate the extension
|
||||
await this.extensionService.activateByEvent(`onUri:${ExtensionIdentifier.toKey(extensionId)}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
registerExtensionHandler(extensionId: string, handler: IURLHandler): void {
|
||||
this.extensionHandlers.set(extensionId, handler);
|
||||
registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void {
|
||||
this.extensionHandlers.set(ExtensionIdentifier.toKey(extensionId), handler);
|
||||
|
||||
const uris = this.uriBuffer.get(extensionId) || [];
|
||||
const uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId)) || [];
|
||||
|
||||
for (const { uri } of uris) {
|
||||
handler.handleURL(uri);
|
||||
}
|
||||
|
||||
this.uriBuffer.delete(extensionId);
|
||||
this.uriBuffer.delete(ExtensionIdentifier.toKey(extensionId));
|
||||
}
|
||||
|
||||
unregisterExtensionHandler(extensionId: string): void {
|
||||
this.extensionHandlers.delete(extensionId);
|
||||
unregisterExtensionHandler(extensionId: ExtensionIdentifier): void {
|
||||
this.extensionHandlers.delete(ExtensionIdentifier.toKey(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];
|
||||
const extension = installedExtensions.filter(e => areSameExtensions(e.identifier, extensionIdentifier))[0];
|
||||
|
||||
// Extension is installed
|
||||
if (extension) {
|
||||
@@ -163,88 +156,91 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||
|
||||
// Extension is not running. Reload the window to handle.
|
||||
if (enabled) {
|
||||
this.dialogService.confirm({
|
||||
const result = await 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;
|
||||
});
|
||||
|
||||
if (!result.confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.reloadAndHandle(uri);
|
||||
}
|
||||
|
||||
// Extension is disabled. Enable the extension and reload the window to handle.
|
||||
else {
|
||||
this.dialogService.confirm({
|
||||
const result = await 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;
|
||||
});
|
||||
|
||||
if (!result.confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.extensionEnablementService.setEnablement([extension], EnablementState.Enabled);
|
||||
await this.reloadAndHandle(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const galleryExtension = await this.galleryService.getCompatibleExtension(extensionIdentifier);
|
||||
|
||||
if (!galleryExtension) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Install the Extension and reload the window to handle.
|
||||
const result = await 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'
|
||||
});
|
||||
|
||||
if (!result.confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
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> {
|
||||
private async reloadAndHandle(url: URI): Promise<void> {
|
||||
this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE);
|
||||
return this.windowService.reloadWindow();
|
||||
await this.windowService.reloadWindow();
|
||||
}
|
||||
|
||||
// forget about all uris buffered more than 5 minutes ago
|
||||
|
||||
@@ -30,7 +30,7 @@ export class RuntimeExtensionsInput extends EditorInput {
|
||||
return true;
|
||||
}
|
||||
|
||||
resolve(): Thenable<any> {
|
||||
resolve(): Promise<any> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user