mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge VS Code 1.21 source code (#1067)
* Initial VS Code 1.21 file copy with patches * A few more merges * Post npm install * Fix batch of build breaks * Fix more build breaks * Fix more build errors * Fix more build breaks * Runtime fixes 1 * Get connection dialog working with some todos * Fix a few packaging issues * Copy several node_modules to package build to fix loader issues * Fix breaks from master * A few more fixes * Make tests pass * First pass of license header updates * Second pass of license header updates * Fix restore dialog issues * Remove add additional themes menu items * fix select box issues where the list doesn't show up * formatting * Fix editor dispose issue * Copy over node modules to correct location on all platforms
This commit is contained in:
193
src/vs/workbench/services/extensions/common/extensions.ts
Normal file
193
src/vs/workbench/services/extensions/common/extensions.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 Severity from 'vs/base/common/severity';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import Event from 'vs/base/common/event';
|
||||
|
||||
export interface IExtensionDescription {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly uuid?: string;
|
||||
readonly displayName?: string;
|
||||
readonly version: string;
|
||||
readonly publisher: string;
|
||||
readonly isBuiltin: boolean;
|
||||
readonly extensionFolderPath: string;
|
||||
readonly extensionDependencies?: string[];
|
||||
readonly activationEvents?: string[];
|
||||
readonly engines: {
|
||||
vscode: string;
|
||||
// {{SQL CARBON EDIT}}
|
||||
sqlops?: string;
|
||||
};
|
||||
readonly main?: string;
|
||||
readonly contributes?: { [point: string]: any; };
|
||||
readonly keywords?: string[];
|
||||
readonly repository?: {
|
||||
url: string;
|
||||
};
|
||||
enableProposedApi?: boolean;
|
||||
}
|
||||
|
||||
export const IExtensionService = createDecorator<IExtensionService>('extensionService');
|
||||
|
||||
export interface IMessage {
|
||||
type: Severity;
|
||||
message: string;
|
||||
source: string;
|
||||
extensionId: string;
|
||||
extensionPointId: string;
|
||||
}
|
||||
|
||||
export interface IExtensionsStatus {
|
||||
messages: IMessage[];
|
||||
activationTimes: ActivationTimes;
|
||||
runtimeErrors: Error[];
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g.
|
||||
* ```
|
||||
* {
|
||||
* startTime: 1511954813493000,
|
||||
* endTime: 1511954835590000,
|
||||
* deltas: [ 100, 1500, 123456, 1500, 100000 ],
|
||||
* ids: [ 'idle', 'self', 'extension1', 'self', 'idle' ]
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface IExtensionHostProfile {
|
||||
/**
|
||||
* Profiling start timestamp in microseconds.
|
||||
*/
|
||||
startTime: number;
|
||||
/**
|
||||
* Profiling end timestamp in microseconds.
|
||||
*/
|
||||
endTime: number;
|
||||
/**
|
||||
* Duration of segment in microseconds.
|
||||
*/
|
||||
deltas: number[];
|
||||
/**
|
||||
* Segment identifier: extension id or one of the four known strings.
|
||||
*/
|
||||
ids: ProfileSegmentId[];
|
||||
|
||||
/**
|
||||
* Get the information as a .cpuprofile.
|
||||
*/
|
||||
data: object;
|
||||
|
||||
/**
|
||||
* Get the aggregated time per segmentId
|
||||
*/
|
||||
getAggregatedTimes(): Map<ProfileSegmentId, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension id or one of the four known program states.
|
||||
*/
|
||||
export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self';
|
||||
|
||||
export class ActivationTimes {
|
||||
constructor(
|
||||
public readonly startup: boolean,
|
||||
public readonly codeLoadingTime: number,
|
||||
public readonly activateCallTime: number,
|
||||
public readonly activateResolvedTime: number,
|
||||
public readonly activationEvent: string
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionPointContribution<T> {
|
||||
readonly description: IExtensionDescription;
|
||||
readonly value: T;
|
||||
|
||||
constructor(description: IExtensionDescription, value: T) {
|
||||
this.description = description;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtensionService {
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* An event emitted when extensions are registered after their extension points got handled.
|
||||
*
|
||||
* This event will also fire on startup to signal the installed extensions.
|
||||
*
|
||||
* @returns the extensions that got registered
|
||||
*/
|
||||
onDidRegisterExtensions: Event<void>;
|
||||
|
||||
/**
|
||||
* @event
|
||||
* Fired when extensions status changes.
|
||||
* The event contains the ids of the extensions that have changed.
|
||||
*/
|
||||
onDidChangeExtensionsStatus: Event<string[]>;
|
||||
|
||||
/**
|
||||
* Send an activation event and activate interested extensions.
|
||||
*/
|
||||
activateByEvent(activationEvent: string): TPromise<void>;
|
||||
|
||||
/**
|
||||
* An promise that resolves when the installed extensions are registered after
|
||||
* their extension points got handled.
|
||||
*/
|
||||
whenInstalledExtensionsRegistered(): TPromise<boolean>;
|
||||
|
||||
/**
|
||||
* Return all registered extensions
|
||||
*/
|
||||
getExtensions(): TPromise<IExtensionDescription[]>;
|
||||
|
||||
/**
|
||||
* Read all contributions to an extension point.
|
||||
*/
|
||||
readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): TPromise<ExtensionPointContribution<T>[]>;
|
||||
|
||||
/**
|
||||
* Get information about extensions status.
|
||||
*/
|
||||
getExtensionsStatus(): { [id: string]: IExtensionsStatus };
|
||||
|
||||
/**
|
||||
* Check if the extension host can be profiled.
|
||||
*/
|
||||
canProfileExtensionHost(): boolean;
|
||||
|
||||
/**
|
||||
* Begin an extension host process profile session.
|
||||
*/
|
||||
startExtensionHostProfile(): TPromise<ProfileSession>;
|
||||
|
||||
/**
|
||||
* Restarts the extension host.
|
||||
*/
|
||||
restartExtensionHost(): void;
|
||||
|
||||
/**
|
||||
* Starts the extension host.
|
||||
*/
|
||||
startExtensionHost(): void;
|
||||
|
||||
/**
|
||||
* Stops the extension host.
|
||||
*/
|
||||
stopExtensionHost(): void;
|
||||
}
|
||||
|
||||
export interface ProfileSession {
|
||||
stop(): TPromise<IExtensionHostProfile>;
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IMessage, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const schemaRegistry = <IJSONContributionRegistry>Registry.as(Extensions.JSONContribution);
|
||||
|
||||
export class ExtensionMessageCollector {
|
||||
|
||||
private readonly _messageHandler: (msg: IMessage) => void;
|
||||
private readonly _extension: IExtensionDescription;
|
||||
private readonly _extensionPointId: string;
|
||||
|
||||
constructor(
|
||||
messageHandler: (msg: IMessage) => void,
|
||||
extension: IExtensionDescription,
|
||||
extensionPointId: string
|
||||
) {
|
||||
this._messageHandler = messageHandler;
|
||||
this._extension = extension;
|
||||
this._extensionPointId = extensionPointId;
|
||||
}
|
||||
|
||||
private _msg(type: Severity, message: string): void {
|
||||
this._messageHandler({
|
||||
type: type,
|
||||
message: message,
|
||||
source: this._extension.extensionFolderPath,
|
||||
extensionId: this._extension.id,
|
||||
extensionPointId: this._extensionPointId
|
||||
});
|
||||
}
|
||||
|
||||
public error(message: string): void {
|
||||
this._msg(Severity.Error, message);
|
||||
}
|
||||
|
||||
public warn(message: string): void {
|
||||
this._msg(Severity.Warning, message);
|
||||
}
|
||||
|
||||
public info(message: string): void {
|
||||
this._msg(Severity.Info, message);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtensionPointUser<T> {
|
||||
description: IExtensionDescription;
|
||||
value: T;
|
||||
collector: ExtensionMessageCollector;
|
||||
}
|
||||
|
||||
export interface IExtensionPointHandler<T> {
|
||||
(extensions: IExtensionPointUser<T>[]): void;
|
||||
}
|
||||
|
||||
export interface IExtensionPoint<T> {
|
||||
name: string;
|
||||
setHandler(handler: IExtensionPointHandler<T>): void;
|
||||
}
|
||||
|
||||
export class ExtensionPoint<T> implements IExtensionPoint<T> {
|
||||
|
||||
public readonly name: string;
|
||||
private _handler: IExtensionPointHandler<T>;
|
||||
private _users: IExtensionPointUser<T>[];
|
||||
private _done: boolean;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this._handler = null;
|
||||
this._users = null;
|
||||
this._done = false;
|
||||
}
|
||||
|
||||
setHandler(handler: IExtensionPointHandler<T>): void {
|
||||
if (this._handler !== null || this._done) {
|
||||
throw new Error('Handler already set!');
|
||||
}
|
||||
this._handler = handler;
|
||||
this._handle();
|
||||
}
|
||||
|
||||
acceptUsers(users: IExtensionPointUser<T>[]): void {
|
||||
if (this._users !== null || this._done) {
|
||||
throw new Error('Users already set!');
|
||||
}
|
||||
this._users = users;
|
||||
this._handle();
|
||||
}
|
||||
|
||||
private _handle(): void {
|
||||
if (this._handler === null || this._users === null) {
|
||||
return;
|
||||
}
|
||||
this._done = true;
|
||||
|
||||
let handler = this._handler;
|
||||
this._handler = null;
|
||||
|
||||
let users = this._users;
|
||||
this._users = null;
|
||||
|
||||
try {
|
||||
handler(users);
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const schemaId = 'vscode://schemas/vscode-extensions';
|
||||
const schema: IJSONSchema = {
|
||||
properties: {
|
||||
engines: {
|
||||
type: 'object',
|
||||
|
||||
properties: {
|
||||
'vscode': {
|
||||
type: 'string',
|
||||
description: nls.localize('vscode.extension.engines.vscode', 'For VS Code extensions, specifies the VS Code version that the extension is compatible with. Cannot be *. For example: ^0.10.5 indicates compatibility with a minimum VS Code version of 0.10.5.'),
|
||||
default: '^0.10.0',
|
||||
}
|
||||
}
|
||||
},
|
||||
publisher: {
|
||||
description: nls.localize('vscode.extension.publisher', 'The publisher of the VS Code extension.'),
|
||||
type: 'string'
|
||||
},
|
||||
displayName: {
|
||||
description: nls.localize('vscode.extension.displayName', 'The display name for the extension used in the VS Code gallery.'),
|
||||
type: 'string'
|
||||
},
|
||||
categories: {
|
||||
description: nls.localize('vscode.extension.categories', 'The categories used by the VS Code gallery to categorize the extension.'),
|
||||
type: 'array',
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: ['Languages', 'Snippets', 'Linters', 'Themes', 'Debuggers', 'Other', 'Keymaps', 'Formatters', 'Extension Packs', 'SCM Providers', 'Azure', 'Language Packs']
|
||||
}
|
||||
},
|
||||
galleryBanner: {
|
||||
type: 'object',
|
||||
description: nls.localize('vscode.extension.galleryBanner', 'Banner used in the VS Code marketplace.'),
|
||||
properties: {
|
||||
color: {
|
||||
description: nls.localize('vscode.extension.galleryBanner.color', 'The banner color on the VS Code marketplace page header.'),
|
||||
type: 'string'
|
||||
},
|
||||
theme: {
|
||||
description: nls.localize('vscode.extension.galleryBanner.theme', 'The color theme for the font used in the banner.'),
|
||||
type: 'string',
|
||||
enum: ['dark', 'light']
|
||||
}
|
||||
}
|
||||
},
|
||||
contributes: {
|
||||
description: nls.localize('vscode.extension.contributes', 'All contributions of the VS Code extension represented by this package.'),
|
||||
type: 'object',
|
||||
properties: {
|
||||
// extensions will fill in
|
||||
},
|
||||
default: {}
|
||||
},
|
||||
preview: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('vscode.extension.preview', 'Sets the extension to be flagged as a Preview in the Marketplace.'),
|
||||
},
|
||||
activationEvents: {
|
||||
description: nls.localize('vscode.extension.activationEvents', 'Activation events for the VS Code extension.'),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
defaultSnippets: [
|
||||
{
|
||||
label: 'onLanguage',
|
||||
description: nls.localize('vscode.extension.activationEvents.onLanguage', 'An activation event emitted whenever a file that resolves to the specified language gets opened.'),
|
||||
body: 'onLanguage:${1:languageId}'
|
||||
},
|
||||
{
|
||||
label: 'onCommand',
|
||||
description: nls.localize('vscode.extension.activationEvents.onCommand', 'An activation event emitted whenever the specified command gets invoked.'),
|
||||
body: 'onCommand:${2:commandId}'
|
||||
},
|
||||
{
|
||||
label: 'onDebug',
|
||||
description: nls.localize('vscode.extension.activationEvents.onDebug', 'An activation event emitted whenever a user is about to start debugging or about to setup debug configurations.'),
|
||||
body: 'onDebug'
|
||||
},
|
||||
{
|
||||
label: 'onDebugInitialConfigurations',
|
||||
description: nls.localize('vscode.extension.activationEvents.onDebugInitialConfigurations', 'An activation event emitted whenever a "launch.json" needs to be created (and all provideDebugConfigurations methods need to be called).'),
|
||||
body: 'onDebugInitialConfigurations'
|
||||
},
|
||||
{
|
||||
label: 'onDebugResolve',
|
||||
description: nls.localize('vscode.extension.activationEvents.onDebugResolve', 'An activation event emitted whenever a debug session with the specific type is about to be launched (and a corresponding resolveDebugConfiguration method needs to be called).'),
|
||||
body: 'onDebugResolve:${6:type}'
|
||||
},
|
||||
{
|
||||
label: 'workspaceContains',
|
||||
description: nls.localize('vscode.extension.activationEvents.workspaceContains', 'An activation event emitted whenever a folder is opened that contains at least a file matching the specified glob pattern.'),
|
||||
body: 'workspaceContains:${4:filePattern}'
|
||||
},
|
||||
{
|
||||
label: 'onView',
|
||||
body: 'onView:${5:viewId}',
|
||||
description: nls.localize('vscode.extension.activationEvents.onView', 'An activation event emitted whenever the specified view is expanded.'),
|
||||
},
|
||||
{
|
||||
label: '*',
|
||||
description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'),
|
||||
body: '*'
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
badges: {
|
||||
type: 'array',
|
||||
description: nls.localize('vscode.extension.badges', 'Array of badges to display in the sidebar of the Marketplace\'s extension page.'),
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['url', 'href', 'description'],
|
||||
properties: {
|
||||
url: {
|
||||
type: 'string',
|
||||
description: nls.localize('vscode.extension.badges.url', 'Badge image URL.')
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
description: nls.localize('vscode.extension.badges.href', 'Badge link.')
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: nls.localize('vscode.extension.badges.description', 'Badge description.')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
extensionDependencies: {
|
||||
description: nls.localize('vscode.extension.extensionDependencies', 'Dependencies to other extensions. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp.'),
|
||||
type: 'array',
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: EXTENSION_IDENTIFIER_PATTERN
|
||||
}
|
||||
},
|
||||
scripts: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'vscode:prepublish': {
|
||||
description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a VS Code extension.'),
|
||||
type: 'string'
|
||||
},
|
||||
'vscode:uninstall': {
|
||||
description: nls.localize('vscode.extension.scripts.uninstall', 'Uninstall hook for VS Code extension. Script that gets executed when the extension is completely uninstalled from VS Code which is when VS Code is restarted (shutdown and start) after the extension is uninstalled. Only Node scripts are supported.'),
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
description: nls.localize('vscode.extension.icon', 'The path to a 128x128 pixel icon.')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class ExtensionsRegistryImpl {
|
||||
|
||||
private _extensionPoints: { [extPoint: string]: ExtensionPoint<any>; };
|
||||
|
||||
constructor() {
|
||||
this._extensionPoints = {};
|
||||
}
|
||||
|
||||
public registerExtensionPoint<T>(extensionPoint: string, deps: IExtensionPoint<any>[], jsonSchema: IJSONSchema): IExtensionPoint<T> {
|
||||
if (hasOwnProperty.call(this._extensionPoints, extensionPoint)) {
|
||||
throw new Error('Duplicate extension point: ' + extensionPoint);
|
||||
}
|
||||
let result = new ExtensionPoint<T>(extensionPoint);
|
||||
this._extensionPoints[extensionPoint] = result;
|
||||
|
||||
schema.properties['contributes'].properties[extensionPoint] = jsonSchema;
|
||||
schemaRegistry.registerSchema(schemaId, schema);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public getExtensionPoints(): ExtensionPoint<any>[] {
|
||||
return Object.keys(this._extensionPoints).map(point => this._extensionPoints[point]);
|
||||
}
|
||||
}
|
||||
|
||||
const PRExtensions = {
|
||||
ExtensionsRegistry: 'ExtensionsRegistry'
|
||||
};
|
||||
Registry.add(PRExtensions.ExtensionsRegistry, new ExtensionsRegistryImpl());
|
||||
export const ExtensionsRegistry: ExtensionsRegistryImpl = Registry.as(PRExtensions.ExtensionsRegistry);
|
||||
|
||||
schemaRegistry.registerSchema(schemaId, schema);
|
||||
@@ -7,13 +7,11 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { stringify } from 'vs/base/common/marshalling';
|
||||
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 { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
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';
|
||||
@@ -27,7 +25,7 @@ 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 { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
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';
|
||||
@@ -36,6 +34,9 @@ import { EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_C
|
||||
import { IDisposable, dispose } 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 { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
export class ExtensionHostProcessWorker {
|
||||
|
||||
@@ -63,7 +64,7 @@ export class ExtensionHostProcessWorker {
|
||||
constructor(
|
||||
/* intentionally not injected */private readonly _extensionService: IExtensionService,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@IMessageService private readonly _messageService: IMessageService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IWindowsService private readonly _windowsService: IWindowsService,
|
||||
@IWindowService private readonly _windowService: IWindowService,
|
||||
@IBroadcastService private readonly _broadcastService: IBroadcastService,
|
||||
@@ -71,7 +72,9 @@ export class ExtensionHostProcessWorker {
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@IWorkspaceConfigurationService private readonly _configurationService: IWorkspaceConfigurationService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@ICrashReporterService private readonly _crashReporterService: ICrashReporterService
|
||||
@ICrashReporterService private readonly _crashReporterService: ICrashReporterService,
|
||||
@IChoiceService private readonly _choiceService: IChoiceService,
|
||||
@ILogService private readonly _logService: ILogService
|
||||
) {
|
||||
// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
|
||||
this._isExtensionDevHost = this._environmentService.isExtensionDevelopment;
|
||||
@@ -143,7 +146,6 @@ export class ExtensionHostProcessWorker {
|
||||
VSCODE_WINDOW_ID: String(this._windowService.getCurrentWindowId()),
|
||||
VSCODE_IPC_HOOK_EXTHOST: pipeName,
|
||||
VSCODE_HANDLES_UNCAUGHT_ERRORS: true,
|
||||
ELECTRON_NO_ASAR: '1',
|
||||
VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || product.quality !== 'stable' || this._environmentService.verbose)
|
||||
}),
|
||||
// We only detach the extension host on windows. Linux and Mac orphan by default
|
||||
@@ -236,7 +238,11 @@ export class ExtensionHostProcessWorker {
|
||||
? 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.")
|
||||
: nls.localize('extensionHostProcess.startupFail', "Extension host did not start in 10 seconds, that might be a problem.");
|
||||
|
||||
this._messageService.show(Severity.Warning, msg);
|
||||
this._choiceService.choose(Severity.Warning, msg, [nls.localize('reloadWindow', "Reload Window")]).then(choice => {
|
||||
if (choice === 0) {
|
||||
this._windowService.reloadWindow();
|
||||
}
|
||||
});
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
@@ -275,6 +281,8 @@ export class ExtensionHostProcessWorker {
|
||||
let startPort = 9333;
|
||||
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 findFreePort(startPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */).then(port => {
|
||||
@@ -329,7 +337,7 @@ export class ExtensionHostProcessWorker {
|
||||
|
||||
if (msg === 'ready') {
|
||||
// 1) Extension Host is ready to receive messages, initialize it
|
||||
this._createExtHostInitData().then(data => protocol.send(stringify(data)));
|
||||
this._createExtHostInitData().then(data => protocol.send(JSON.stringify(data)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -356,7 +364,7 @@ export class ExtensionHostProcessWorker {
|
||||
|
||||
private _createExtHostInitData(): TPromise<IInitData> {
|
||||
return TPromise.join<any>([this._telemetryService.getTelemetryInfo(), this._extensionService.getExtensions()]).then(([telemetryInfo, extensionDescriptions]) => {
|
||||
const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: [] };
|
||||
const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} };
|
||||
const r: IInitData = {
|
||||
parentPid: process.pid,
|
||||
environment: {
|
||||
@@ -374,11 +382,12 @@ export class ExtensionHostProcessWorker {
|
||||
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(this._configurationService.keys().default) } : configurationData,
|
||||
configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData,
|
||||
telemetryInfo,
|
||||
args: this._environmentService.args,
|
||||
execPath: this._environmentService.execPath,
|
||||
windowId: this._windowService.getCurrentWindowId()
|
||||
windowId: this._windowService.getCurrentWindowId(),
|
||||
logLevel: this._logService.getLevel()
|
||||
};
|
||||
return r;
|
||||
});
|
||||
@@ -416,7 +425,7 @@ export class ExtensionHostProcessWorker {
|
||||
|
||||
this._lastExtensionHostError = errorMessage;
|
||||
|
||||
this._messageService.show(Severity.Error, nls.localize('extensionHostProcess.error', "Error from the extension host: {0}", errorMessage));
|
||||
this._notificationService.error(nls.localize('extensionHostProcess.error', "Error from the extension host: {0}", errorMessage));
|
||||
}
|
||||
|
||||
private _onExtHostProcessExit(code: number, signal: string): void {
|
||||
|
||||
@@ -5,17 +5,10 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IExtensionService, IExtensionDescription, ProfileSession, IExtensionHostProfile, ProfileSegmentId } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionService, IExtensionDescription, ProfileSession, IExtensionHostProfile, ProfileSegmentId } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { realpathSync } from 'vs/base/node/extfs';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { writeFile } from 'vs/base/node/pfs';
|
||||
import * as path from 'path';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { setTimeout } from 'timers';
|
||||
import { Profile, ProfileNode } from 'v8-inspect-profiler';
|
||||
|
||||
export class ExtensionHostProfiler {
|
||||
@@ -128,27 +121,3 @@ export class ExtensionHostProfiler {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CommandsRegistry.registerCommand('exthost.profile.start', async accessor => {
|
||||
const statusbarService = accessor.get(IStatusbarService);
|
||||
const extensionService = accessor.get(IExtensionService);
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
|
||||
const handle = statusbarService.addEntry({ text: localize('message', "$(zap) Profiling Extension Host...") }, StatusbarAlignment.LEFT);
|
||||
|
||||
extensionService.startExtensionHostProfile().then(session => {
|
||||
setTimeout(() => {
|
||||
session.stop().then(result => {
|
||||
result.getAggregatedTimes().forEach((val, index) => {
|
||||
console.log(`${index} : ${Math.round(val / 1000)} ms`);
|
||||
});
|
||||
let profilePath = path.join(environmentService.userHome, 'extHostProfile.cpuprofile');
|
||||
console.log(`Saving profile at ${profilePath}`);
|
||||
return writeFile(profilePath, JSON.stringify(result.data));
|
||||
}).then(() => {
|
||||
handle.dispose();
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,42 +7,95 @@
|
||||
import * as nls from 'vs/nls';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
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, IExtensionHostInformation, ProfileSession, USER_MANIFEST_CACHE_FILE, BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER } from 'vs/platform/extensions/common/extensions';
|
||||
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 } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, BetterMergeId, BetterMergeDisabledNowKey } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
import { ExtensionScanner, ILog, ExtensionScannerInput } from 'vs/workbench/services/extensions/electron-browser/extensionPoints';
|
||||
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
|
||||
import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ExtensionScanner, ILog, ExtensionScannerInput, IExtensionResolver, IExtensionReference, Translations } 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 { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
|
||||
import { MainThreadService } from 'vs/workbench/services/thread/electron-browser/threadService';
|
||||
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 { Action } from 'vs/base/common/actions';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { mark, time } 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 { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
|
||||
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 { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
|
||||
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(msg: IMessage): string {
|
||||
return messageWithSource2(msg.source, msg.message);
|
||||
@@ -61,7 +114,7 @@ const NO_OP_VOID_PROMISE = TPromise.wrap<void>(void 0);
|
||||
export class ExtensionService extends Disposable implements IExtensionService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _onDidRegisterExtensions: Emitter<IExtensionDescription[]>;
|
||||
private _onDidRegisterExtensions: Emitter<void>;
|
||||
|
||||
private _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _installedExtensionsReady: Barrier;
|
||||
@@ -81,7 +134,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; };
|
||||
private _extensionHostExtensionRuntimeErrors: { [id: string]: Error[]; };
|
||||
private _extensionHostProcessWorker: ExtensionHostProcessWorker;
|
||||
private _extensionHostProcessThreadService: MainThreadService;
|
||||
private _extensionHostProcessRPCProtocol: RPCProtocol;
|
||||
private _extensionHostProcessCustomers: IDisposable[];
|
||||
/**
|
||||
* winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object.
|
||||
@@ -90,7 +143,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IMessageService private readonly _messageService: IMessageService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IChoiceService private readonly _choiceService: IChoiceService,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
|
||||
@@ -105,41 +159,56 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
this._extensionsMessages = {};
|
||||
this._allRequestedActivateEvents = Object.create(null);
|
||||
|
||||
this._onDidRegisterExtensions = new Emitter<IExtensionDescription[]>();
|
||||
this._onDidRegisterExtensions = new Emitter<void>();
|
||||
|
||||
this._extensionHostProcessFinishedActivateEvents = Object.create(null);
|
||||
this._extensionHostProcessActivationTimes = Object.create(null);
|
||||
this._extensionHostExtensionRuntimeErrors = Object.create(null);
|
||||
this._extensionHostProcessWorker = null;
|
||||
this._extensionHostProcessThreadService = null;
|
||||
this._extensionHostProcessRPCProtocol = null;
|
||||
this._extensionHostProcessCustomers = [];
|
||||
this._extensionHostProcessProxy = null;
|
||||
|
||||
lifecycleService.when(LifecyclePhase.Running).then(() => {
|
||||
// delay extension host creation and extension scanning
|
||||
// until after workbench is running
|
||||
this._startExtensionHostProcess([]);
|
||||
this._scanAndHandleExtensions();
|
||||
this.startDelayed(lifecycleService);
|
||||
}
|
||||
|
||||
private startDelayed(lifecycleService: ILifecycleService): void {
|
||||
let started = false;
|
||||
const startOnce = () => {
|
||||
if (!started) {
|
||||
started = true;
|
||||
|
||||
this._startExtensionHostProcess([]);
|
||||
this._scanAndHandleExtensions();
|
||||
}
|
||||
};
|
||||
|
||||
// delay extension host creation and extension scanning
|
||||
// until the workbench is restoring. we cannot defer the
|
||||
// extension host more (LifecyclePhase.Running) 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);
|
||||
});
|
||||
|
||||
// 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<IExtensionDescription[]> {
|
||||
public get onDidRegisterExtensions(): Event<void> {
|
||||
return this._onDidRegisterExtensions.event;
|
||||
}
|
||||
|
||||
public getExtensionHostInformation(): IExtensionHostInformation {
|
||||
if (!this._extensionHostProcessWorker) {
|
||||
throw errors.illegalState();
|
||||
}
|
||||
return <IExtensionHostInformation>{
|
||||
inspectPort: this._extensionHostProcessWorker.getInspectPort()
|
||||
};
|
||||
}
|
||||
|
||||
public restartExtensionHost(): void {
|
||||
this._stopExtensionHostProcess();
|
||||
this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
|
||||
@@ -163,9 +232,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
this._extensionHostProcessWorker.dispose();
|
||||
this._extensionHostProcessWorker = null;
|
||||
}
|
||||
if (this._extensionHostProcessThreadService) {
|
||||
this._extensionHostProcessThreadService.dispose();
|
||||
this._extensionHostProcessThreadService = null;
|
||||
if (this._extensionHostProcessRPCProtocol) {
|
||||
this._extensionHostProcessRPCProtocol.dispose();
|
||||
this._extensionHostProcessRPCProtocol = null;
|
||||
}
|
||||
for (let i = 0, len = this._extensionHostProcessCustomers.length; i < len; i++) {
|
||||
const customer = this._extensionHostProcessCustomers[i];
|
||||
@@ -202,16 +271,6 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
|
||||
private _onExtensionHostCrashed(code: number, signal: string): void {
|
||||
const openDevTools = new Action('openDevTools', nls.localize('devTools', "Developer Tools"), '', true, (): TPromise<boolean> => {
|
||||
return this._windowService.openDevTools().then(() => false);
|
||||
});
|
||||
|
||||
const restart = new Action('restart', nls.localize('restart', "Restart Extension Host"), '', true, (): TPromise<boolean> => {
|
||||
this._messageService.hideAll();
|
||||
this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
|
||||
return TPromise.as(true);
|
||||
});
|
||||
|
||||
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
|
||||
this._stopExtensionHostProcess();
|
||||
|
||||
@@ -219,19 +278,27 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
if (code === 87) {
|
||||
message = nls.localize('extensionHostProcess.unresponsiveCrash', "Extension host terminated because it was not responsive.");
|
||||
}
|
||||
this._messageService.show(Severity.Error, {
|
||||
message: message,
|
||||
actions: [
|
||||
openDevTools,
|
||||
restart
|
||||
]
|
||||
|
||||
this._choiceService.choose(Severity.Error, message, [nls.localize('devTools', "Developer Tools"), nls.localize('restart', "Restart Extension Host")]).then(choice => {
|
||||
switch (choice) {
|
||||
case 0 /* Open Dev Tools */:
|
||||
this._windowService.openDevTools();
|
||||
break;
|
||||
case 1 /* Restart Extension Host */:
|
||||
this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _createExtensionHostCustomers(protocol: IMessagePassingProtocol): ExtHostExtensionServiceShape {
|
||||
|
||||
this._extensionHostProcessThreadService = this._instantiationService.createInstance(MainThreadService, protocol);
|
||||
const extHostContext: IExtHostContext = this._extensionHostProcessThreadService;
|
||||
if (logExtensionHostCommunication || this._environmentService.logExtensionHostCommunication) {
|
||||
protocol = asLoggingProtocol(protocol);
|
||||
}
|
||||
|
||||
this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol);
|
||||
const extHostContext: IExtHostContext = this._extensionHostProcessRPCProtocol;
|
||||
|
||||
// Named customers
|
||||
const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers();
|
||||
@@ -239,7 +306,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
const [id, ctor] = namedCustomers[i];
|
||||
const instance = this._instantiationService.createInstance(ctor, extHostContext);
|
||||
this._extensionHostProcessCustomers.push(instance);
|
||||
this._extensionHostProcessThreadService.set(id, instance);
|
||||
this._extensionHostProcessRPCProtocol.set(id, instance);
|
||||
}
|
||||
|
||||
// Customers
|
||||
@@ -252,9 +319,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
// Check that no named customers are missing
|
||||
const expected: ProxyIdentifier<any>[] = Object.keys(MainContext).map((key) => MainContext[key]);
|
||||
this._extensionHostProcessThreadService.assertRegistered(expected);
|
||||
this._extensionHostProcessRPCProtocol.assertRegistered(expected);
|
||||
|
||||
return this._extensionHostProcessThreadService.get(ExtHostContext.ExtHostExtensionService);
|
||||
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);
|
||||
}
|
||||
|
||||
// ---- begin IExtensionService
|
||||
@@ -337,6 +404,10 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
return result;
|
||||
}
|
||||
|
||||
public canProfileExtensionHost(): boolean {
|
||||
return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort());
|
||||
}
|
||||
|
||||
public startExtensionHostProfile(): TPromise<ProfileSession> {
|
||||
if (this._extensionHostProcessWorker) {
|
||||
let port = this._extensionHostProcessWorker.getInspectPort();
|
||||
@@ -373,7 +444,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
mark('extensionHostReady');
|
||||
this._installedExtensionsReady.open();
|
||||
this._onDidRegisterExtensions.fire(availableExtensions);
|
||||
this._onDidRegisterExtensions.fire(void 0);
|
||||
this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id));
|
||||
});
|
||||
}
|
||||
@@ -383,7 +454,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
this._logOrShowMessage(severity, this._isDev ? messageWithSource2(source, message) : message);
|
||||
});
|
||||
|
||||
return ExtensionService._scanInstalledExtensions(this._instantiationService, this._messageService, this._environmentService, log)
|
||||
return ExtensionService._scanInstalledExtensions(this._windowService, this._choiceService, this._environmentService, log)
|
||||
.then(({ system, user, development }) => {
|
||||
this._extensionEnablementService.migrateToIdentifiers(user); // TODO: @sandy Remove it after couple of milestones
|
||||
return this._extensionEnablementService.getDisabledExtensions()
|
||||
@@ -393,8 +464,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
let userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }];
|
||||
|
||||
system.forEach((systemExtension) => {
|
||||
// Disabling system extensions is not supported
|
||||
result[systemExtension.id] = systemExtension;
|
||||
if (disabledExtensions.every(disabled => !areSameExtensions(disabled, systemExtension))) {
|
||||
result[systemExtension.id] = systemExtension;
|
||||
}
|
||||
});
|
||||
|
||||
user.forEach((userExtension) => {
|
||||
@@ -471,13 +543,17 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
}
|
||||
|
||||
private static async _validateExtensionsCache(instantiationService: IInstantiationService, messageService: IMessageService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): TPromise<void> {
|
||||
private static async _validateExtensionsCache(windowService: IWindowService, choiceService: IChoiceService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): TPromise<void> {
|
||||
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
|
||||
const cacheFile = path.join(cacheFolder, cacheKey);
|
||||
|
||||
const expected = 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)) {
|
||||
@@ -492,13 +568,10 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
let message = nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window.");
|
||||
messageService.show(Severity.Info, {
|
||||
message: message,
|
||||
actions: [
|
||||
instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL),
|
||||
CloseAction
|
||||
]
|
||||
choiceService.choose(Severity.Error, nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."), [nls.localize('reloadWindow', "Reload Window")]).then(choice => {
|
||||
if (choice === 0) {
|
||||
windowService.reloadWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -533,7 +606,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
}
|
||||
|
||||
private static async _scanExtensionsWithCache(instantiationService: IInstantiationService, messageService: IMessageService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
|
||||
private static async _scanExtensionsWithCache(windowService: IWindowService, choiceService: IChoiceService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
|
||||
if (input.devMode) {
|
||||
// Do not cache when running out of sources...
|
||||
return ExtensionScanner.scanExtensions(input, log);
|
||||
@@ -551,7 +624,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
// Validate the cache asynchronously after 5s
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await this._validateExtensionsCache(instantiationService, messageService, environmentService, cacheKey, input);
|
||||
await this._validateExtensionsCache(windowService, choiceService, environmentService, cacheKey, input);
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
}
|
||||
@@ -573,45 +646,100 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _scanInstalledExtensions(instantiationService: IInstantiationService, messageService: IMessageService, environmentService: IEnvironmentService, log: ILog): TPromise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
|
||||
const version = pkg.version;
|
||||
const commit = product.commit;
|
||||
const devMode = !!process.env['VSCODE_DEV'];
|
||||
const locale = platform.locale;
|
||||
private static _scanInstalledExtensions(windowService: IWindowService, choiceService: IChoiceService, environmentService: IEnvironmentService, log: ILog): TPromise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
|
||||
|
||||
const builtinExtensions = this._scanExtensionsWithCache(
|
||||
instantiationService,
|
||||
messageService,
|
||||
environmentService,
|
||||
BUILTIN_MANIFEST_CACHE_FILE,
|
||||
new ExtensionScannerInput(version, commit, locale, devMode, SystemExtensionsRoot, true),
|
||||
log
|
||||
);
|
||||
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));
|
||||
|
||||
const userExtensions = (
|
||||
environmentService.disableExtensions || !environmentService.extensionsPath
|
||||
? TPromise.as([])
|
||||
: this._scanExtensionsWithCache(
|
||||
instantiationService,
|
||||
messageService,
|
||||
environmentService,
|
||||
USER_MANIFEST_CACHE_FILE,
|
||||
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false),
|
||||
log
|
||||
)
|
||||
);
|
||||
return translationConfig.then((translations) => {
|
||||
const version = pkg.version;
|
||||
const commit = product.commit;
|
||||
const devMode = !!process.env['VSCODE_DEV'];
|
||||
const locale = platform.locale;
|
||||
|
||||
// Always load developed extensions while extensions development
|
||||
const developedExtensions = (
|
||||
environmentService.isExtensionDevelopment
|
||||
? ExtensionScanner.scanOneOrMultipleExtensions(
|
||||
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionDevelopmentPath, false), log
|
||||
)
|
||||
: TPromise.as([])
|
||||
);
|
||||
const builtinExtensions = this._scanExtensionsWithCache(
|
||||
windowService,
|
||||
choiceService,
|
||||
environmentService,
|
||||
BUILTIN_MANIFEST_CACHE_FILE,
|
||||
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, translations),
|
||||
log
|
||||
);
|
||||
|
||||
return TPromise.join([builtinExtensions, userExtensions, developedExtensions])
|
||||
.then((extensionDescriptions: IExtensionDescription[][]) => {
|
||||
let finalBuiltinExtensions: TPromise<IExtensionDescription[]> = 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, 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.extensionFolderPath);
|
||||
const bLastSegment = path.basename(b.extensionFolderPath);
|
||||
if (aLastSegment < bLastSegment) {
|
||||
return -1;
|
||||
}
|
||||
if (aLastSegment > bLastSegment) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return resultArr;
|
||||
});
|
||||
}
|
||||
|
||||
const userExtensions = (
|
||||
environmentService.disableExtensions || !environmentService.extensionsPath
|
||||
? TPromise.as([])
|
||||
: this._scanExtensionsWithCache(
|
||||
windowService,
|
||||
choiceService,
|
||||
environmentService,
|
||||
USER_MANIFEST_CACHE_FILE,
|
||||
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, 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, 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];
|
||||
@@ -620,6 +748,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
log.error('', err);
|
||||
return { system: [], user: [], development: [] };
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static _handleExtensionPoint<T>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void {
|
||||
@@ -641,7 +771,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
private _showMessageToUser(severity: Severity, msg: string): void {
|
||||
if (severity === Severity.Error || severity === Severity.Warning) {
|
||||
this._messageService.show(severity, msg);
|
||||
this._notificationService.notify({ severity, message: msg });
|
||||
} else {
|
||||
this._logMessageInConsole(severity, msg);
|
||||
}
|
||||
@@ -695,6 +825,22 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
}
|
||||
|
||||
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[];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
|
||||
|
||||
@@ -7,23 +7,52 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { groupBy, values } from 'vs/base/common/collections';
|
||||
import { join, normalize, extname } from 'path';
|
||||
import * as json from 'vs/base/common/json';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { isValidExtensionDescription } from 'vs/platform/extensions/node/extensionValidator';
|
||||
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
|
||||
import * as semver from 'semver';
|
||||
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
|
||||
import { groupByExtension, getGalleryExtensionId, getLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
|
||||
const MANIFEST_FILE = 'package.json';
|
||||
|
||||
export interface Translations {
|
||||
[id: string]: string;
|
||||
}
|
||||
|
||||
namespace Translations {
|
||||
export function equals(a: Translations, b: Translations): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
let aKeys = Object.keys(a);
|
||||
let bKeys: Set<string> = new Set<string>();
|
||||
for (let key of Object.keys(b)) {
|
||||
bKeys.add(key);
|
||||
}
|
||||
if (aKeys.length !== bKeys.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let key of aKeys) {
|
||||
if (a[key] !== b[key]) {
|
||||
return false;
|
||||
}
|
||||
bKeys.delete(key);
|
||||
}
|
||||
return bKeys.size === 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface NlsConfiguration {
|
||||
readonly devMode: boolean;
|
||||
readonly locale: string;
|
||||
readonly pseudo: boolean;
|
||||
readonly translations: Translations;
|
||||
}
|
||||
|
||||
export interface ILog {
|
||||
@@ -89,37 +118,90 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
}
|
||||
|
||||
public replaceNLS(extensionDescription: IExtensionDescription): TPromise<IExtensionDescription> {
|
||||
interface MessageBag {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface TranslationBundle {
|
||||
contents: {
|
||||
package: MessageBag;
|
||||
};
|
||||
}
|
||||
|
||||
interface LocalizedMessages {
|
||||
values: MessageBag;
|
||||
default: string;
|
||||
}
|
||||
|
||||
const reportErrors = (localized: string, errors: json.ParseError[]): void => {
|
||||
errors.forEach((error) => {
|
||||
this._log.error(this._absoluteFolderPath, nls.localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localized, getParseErrorMessage(error.error)));
|
||||
});
|
||||
};
|
||||
|
||||
let extension = extname(this._absoluteManifestPath);
|
||||
let basename = this._absoluteManifestPath.substr(0, this._absoluteManifestPath.length - extension.length);
|
||||
|
||||
return pfs.fileExists(basename + '.nls' + extension).then(exists => {
|
||||
if (!exists) {
|
||||
return extensionDescription;
|
||||
}
|
||||
return ExtensionManifestNLSReplacer.findMessageBundles(this._nlsConfig, basename).then((messageBundle) => {
|
||||
if (!messageBundle.localized) {
|
||||
return extensionDescription;
|
||||
const translationId = `${extensionDescription.publisher}.${extensionDescription.name}`;
|
||||
let translationPath = this._nlsConfig.translations[translationId];
|
||||
let localizedMessages: TPromise<LocalizedMessages>;
|
||||
if (translationPath) {
|
||||
localizedMessages = pfs.readFile(translationPath, 'utf8').then<LocalizedMessages, LocalizedMessages>((content) => {
|
||||
let errors: json.ParseError[] = [];
|
||||
let translationBundle: TranslationBundle = json.parse(content, errors);
|
||||
if (errors.length > 0) {
|
||||
reportErrors(translationPath, errors);
|
||||
return { values: undefined, default: `${basename}.nls.json` };
|
||||
} else {
|
||||
let values = translationBundle.contents ? translationBundle.contents.package : undefined;
|
||||
return { values: values, default: `${basename}.nls.json` };
|
||||
}
|
||||
return pfs.readFile(messageBundle.localized).then(messageBundleContent => {
|
||||
let errors: json.ParseError[] = [];
|
||||
let messages: { [key: string]: string; } = json.parse(messageBundleContent.toString(), errors);
|
||||
|
||||
return ExtensionManifestNLSReplacer.resolveOriginalMessageBundle(messageBundle.original, errors).then(originalMessages => {
|
||||
}, (error) => {
|
||||
return { values: undefined, default: `${basename}.nls.json` };
|
||||
});
|
||||
} else {
|
||||
localizedMessages = pfs.fileExists(basename + '.nls' + extension).then<LocalizedMessages, undefined | LocalizedMessages>(exists => {
|
||||
if (!exists) {
|
||||
return undefined;
|
||||
}
|
||||
return ExtensionManifestNLSReplacer.findMessageBundles(this._nlsConfig, basename).then((messageBundle) => {
|
||||
if (!messageBundle.localized) {
|
||||
return { values: undefined, default: messageBundle.original };
|
||||
}
|
||||
return pfs.readFile(messageBundle.localized, 'utf8').then(messageBundleContent => {
|
||||
let errors: json.ParseError[] = [];
|
||||
let messages: MessageBag = json.parse(messageBundleContent, errors);
|
||||
if (errors.length > 0) {
|
||||
errors.forEach((error) => {
|
||||
this._log.error(this._absoluteFolderPath, nls.localize('jsonsParseFail', "Failed to parse {0} or {1}: {2}.", messageBundle.localized, messageBundle.original, getParseErrorMessage(error.error)));
|
||||
});
|
||||
return extensionDescription;
|
||||
reportErrors(messageBundle.localized, errors);
|
||||
return { values: undefined, default: messageBundle.original };
|
||||
}
|
||||
|
||||
ExtensionManifestNLSReplacer._replaceNLStrings(this._nlsConfig, extensionDescription, messages, originalMessages, this._log, this._absoluteFolderPath);
|
||||
return extensionDescription;
|
||||
return { values: messages, default: messageBundle.original };
|
||||
}, (err) => {
|
||||
return { values: undefined, default: messageBundle.original };
|
||||
});
|
||||
}, (err) => {
|
||||
this._log.error(this._absoluteFolderPath, nls.localize('fileReadFail', "Cannot read file {0}: {1}.", messageBundle.localized, err.message));
|
||||
return null;
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return localizedMessages.then((localizedMessages) => {
|
||||
if (localizedMessages === undefined) {
|
||||
return extensionDescription;
|
||||
}
|
||||
let errors: json.ParseError[] = [];
|
||||
// resolveOriginalMessageBundle returns null if localizedMessages.default === undefined;
|
||||
return ExtensionManifestNLSReplacer.resolveOriginalMessageBundle(localizedMessages.default, errors).then((defaults) => {
|
||||
if (errors.length > 0) {
|
||||
reportErrors(localizedMessages.default, errors);
|
||||
return extensionDescription;
|
||||
}
|
||||
const localized = localizedMessages.values || Object.create(null);
|
||||
ExtensionManifestNLSReplacer._replaceNLStrings(this._nlsConfig, extensionDescription, localized, defaults, this._log, this._absoluteFolderPath);
|
||||
return extensionDescription;
|
||||
});
|
||||
}, (err) => {
|
||||
return extensionDescription;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,6 +213,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
if (originalMessageBundle) {
|
||||
pfs.readFile(originalMessageBundle).then(originalBundleContent => {
|
||||
c(json.parse(originalBundleContent.toString(), errors));
|
||||
}, (err) => {
|
||||
c(null);
|
||||
});
|
||||
} else {
|
||||
c(null);
|
||||
@@ -180,6 +264,11 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
if (length > 1 && str[0] === '%' && str[length - 1] === '%') {
|
||||
let messageKey = str.substr(1, length - 2);
|
||||
let message = messages[messageKey];
|
||||
// If the messages come from a language pack they might miss some keys
|
||||
// Fill them from the original messages.
|
||||
if (message === undefined && originalMessages) {
|
||||
message = originalMessages[messageKey];
|
||||
}
|
||||
if (message) {
|
||||
if (nlsConfig.pseudo) {
|
||||
// FF3B and FF3D is the Unicode zenkaku representation for [ and ]
|
||||
@@ -231,7 +320,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
|
||||
extensionDescription.isBuiltin = this._isBuiltin;
|
||||
|
||||
let notices: string[] = [];
|
||||
if (!isValidExtensionDescription(this._ourVersion, this._absoluteFolderPath, extensionDescription, notices)) {
|
||||
if (!ExtensionManifestValidator.isValidExtensionDescription(this._ourVersion, this._absoluteFolderPath, extensionDescription, notices)) {
|
||||
notices.forEach((error) => {
|
||||
this._log.error(this._absoluteFolderPath, error);
|
||||
});
|
||||
@@ -255,6 +344,93 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
|
||||
|
||||
return extensionDescription;
|
||||
}
|
||||
|
||||
private static isValidExtensionDescription(version: string, extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean {
|
||||
|
||||
if (!ExtensionManifestValidator.baseIsValidExtensionDescription(extensionFolderPath, extensionDescription, notices)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!semver.valid(extensionDescription.version)) {
|
||||
notices.push(nls.localize('notSemver', "Extension version is not semver compatible."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValidExtensionVersion(version, extensionDescription, notices);
|
||||
}
|
||||
|
||||
private static baseIsValidExtensionDescription(extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean {
|
||||
if (!extensionDescription) {
|
||||
notices.push(nls.localize('extensionDescription.empty', "Got empty extension description"));
|
||||
return false;
|
||||
}
|
||||
if (typeof extensionDescription.publisher !== 'string') {
|
||||
notices.push(nls.localize('extensionDescription.publisher', "property `{0}` is mandatory and must be of type `string`", 'publisher'));
|
||||
return false;
|
||||
}
|
||||
if (typeof extensionDescription.name !== 'string') {
|
||||
notices.push(nls.localize('extensionDescription.name', "property `{0}` is mandatory and must be of type `string`", 'name'));
|
||||
return false;
|
||||
}
|
||||
if (typeof extensionDescription.version !== 'string') {
|
||||
notices.push(nls.localize('extensionDescription.version', "property `{0}` is mandatory and must be of type `string`", 'version'));
|
||||
return false;
|
||||
}
|
||||
if (!extensionDescription.engines) {
|
||||
notices.push(nls.localize('extensionDescription.engines', "property `{0}` is mandatory and must be of type `object`", 'engines'));
|
||||
return false;
|
||||
}
|
||||
if (typeof extensionDescription.engines.vscode !== 'string') {
|
||||
notices.push(nls.localize('extensionDescription.engines.vscode', "property `{0}` is mandatory and must be of type `string`", 'engines.vscode'));
|
||||
return false;
|
||||
}
|
||||
if (typeof extensionDescription.extensionDependencies !== 'undefined') {
|
||||
if (!ExtensionManifestValidator._isStringArray(extensionDescription.extensionDependencies)) {
|
||||
notices.push(nls.localize('extensionDescription.extensionDependencies', "property `{0}` can be omitted or must be of type `string[]`", 'extensionDependencies'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof extensionDescription.activationEvents !== 'undefined') {
|
||||
if (!ExtensionManifestValidator._isStringArray(extensionDescription.activationEvents)) {
|
||||
notices.push(nls.localize('extensionDescription.activationEvents1', "property `{0}` can be omitted or must be of type `string[]`", 'activationEvents'));
|
||||
return false;
|
||||
}
|
||||
if (typeof extensionDescription.main === 'undefined') {
|
||||
notices.push(nls.localize('extensionDescription.activationEvents2', "properties `{0}` and `{1}` must both be specified or must both be omitted", 'activationEvents', 'main'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof extensionDescription.main !== 'undefined') {
|
||||
if (typeof extensionDescription.main !== 'string') {
|
||||
notices.push(nls.localize('extensionDescription.main1', "property `{0}` can be omitted or must be of type `string`", 'main'));
|
||||
return false;
|
||||
} else {
|
||||
let normalizedAbsolutePath = join(extensionFolderPath, extensionDescription.main);
|
||||
|
||||
if (normalizedAbsolutePath.indexOf(extensionFolderPath)) {
|
||||
notices.push(nls.localize('extensionDescription.main2', "Expected `main` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", normalizedAbsolutePath, extensionFolderPath));
|
||||
// not a failure case
|
||||
}
|
||||
}
|
||||
if (typeof extensionDescription.activationEvents === 'undefined') {
|
||||
notices.push(nls.localize('extensionDescription.main3', "properties `{0}` and `{1}` must both be specified or must both be omitted", 'activationEvents', 'main'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static _isStringArray(arr: string[]): boolean {
|
||||
if (!Array.isArray(arr)) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
if (typeof arr[i] !== 'string') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionScannerInput {
|
||||
@@ -267,7 +443,8 @@ export class ExtensionScannerInput {
|
||||
public readonly locale: string,
|
||||
public readonly devMode: boolean,
|
||||
public readonly absoluteFolderPath: string,
|
||||
public readonly isBuiltin: boolean
|
||||
public readonly isBuiltin: boolean,
|
||||
public readonly tanslations: Translations
|
||||
) {
|
||||
// Keep empty!! (JSON.parse)
|
||||
}
|
||||
@@ -276,7 +453,8 @@ export class ExtensionScannerInput {
|
||||
return {
|
||||
devMode: input.devMode,
|
||||
locale: input.locale,
|
||||
pseudo: input.locale === 'pseudo'
|
||||
pseudo: input.locale === 'pseudo',
|
||||
translations: input.tanslations
|
||||
};
|
||||
}
|
||||
|
||||
@@ -289,10 +467,30 @@ export class ExtensionScannerInput {
|
||||
&& a.absoluteFolderPath === b.absoluteFolderPath
|
||||
&& a.isBuiltin === b.isBuiltin
|
||||
&& a.mtime === b.mtime
|
||||
&& Translations.equals(a.tanslations, b.tanslations)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExtensionReference {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface IExtensionResolver {
|
||||
resolveExtensions(): TPromise<IExtensionReference[]>;
|
||||
}
|
||||
|
||||
class DefaultExtensionResolver implements IExtensionResolver {
|
||||
|
||||
constructor(private root: string) { }
|
||||
|
||||
resolveExtensions(): TPromise<IExtensionReference[]> {
|
||||
return pfs.readDirsInDir(this.root)
|
||||
.then(folders => folders.map(name => ({ name, path: join(this.root, name) })));
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionScanner {
|
||||
|
||||
/**
|
||||
@@ -322,10 +520,14 @@ export class ExtensionScanner {
|
||||
/**
|
||||
* Scan a list of extensions defined in `absoluteFolderPath`
|
||||
*/
|
||||
public static async scanExtensions(input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
|
||||
public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver: IExtensionResolver = null): TPromise<IExtensionDescription[]> {
|
||||
const absoluteFolderPath = input.absoluteFolderPath;
|
||||
const isBuiltin = input.isBuiltin;
|
||||
|
||||
if (!resolver) {
|
||||
resolver = new DefaultExtensionResolver(absoluteFolderPath);
|
||||
}
|
||||
|
||||
try {
|
||||
let obsolete: { [folderName: string]: boolean; } = {};
|
||||
if (!isBuiltin) {
|
||||
@@ -337,40 +539,37 @@ export class ExtensionScanner {
|
||||
}
|
||||
}
|
||||
|
||||
const rawFolders = await pfs.readDirsInDir(absoluteFolderPath);
|
||||
let folders: string[] = null;
|
||||
if (isBuiltin) {
|
||||
folders = rawFolders;
|
||||
} else {
|
||||
let refs = await resolver.resolveExtensions();
|
||||
|
||||
// Ensure the same extension order
|
||||
refs.sort((a, b) => a.name < b.name ? -1 : 1);
|
||||
|
||||
if (!isBuiltin) {
|
||||
// TODO: align with extensionsService
|
||||
const nonGallery: string[] = [];
|
||||
const gallery: { folder: string; id: string; version: string; }[] = [];
|
||||
|
||||
rawFolders.forEach(folder => {
|
||||
if (obsolete[folder]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { id, version } = getIdAndVersionFromLocalExtensionId(folder);
|
||||
const nonGallery: IExtensionReference[] = [];
|
||||
const gallery: IExtensionReference[] = [];
|
||||
|
||||
refs.forEach(ref => {
|
||||
const { id, version } = getIdAndVersionFromLocalExtensionId(ref.name);
|
||||
if (!id || !version) {
|
||||
nonGallery.push(folder);
|
||||
return;
|
||||
nonGallery.push(ref);
|
||||
} else {
|
||||
gallery.push(ref);
|
||||
}
|
||||
|
||||
gallery.push({ folder, id, version });
|
||||
});
|
||||
|
||||
const byId = values(groupBy(gallery, p => p.id));
|
||||
const latest = byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0])
|
||||
.map(a => a.folder);
|
||||
|
||||
folders = [...nonGallery, ...latest];
|
||||
refs = [...nonGallery, ...gallery];
|
||||
}
|
||||
|
||||
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
|
||||
let extensionDescriptions = await TPromise.join(folders.map(f => this.scanExtension(input.ourVersion, log, join(absoluteFolderPath, f), isBuiltin, nlsConfig)));
|
||||
extensionDescriptions = extensionDescriptions.filter(item => item !== null);
|
||||
let extensionDescriptions = await TPromise.join(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, nlsConfig)));
|
||||
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[getLocalExtensionId(getGalleryExtensionId(item.publisher, item.name), item.version)]);
|
||||
|
||||
if (!isBuiltin) {
|
||||
// Filter out outdated extensions
|
||||
const byExtension: IExtensionDescription[][] = groupByExtension(extensionDescriptions, e => ({ id: e.id, uuid: e.uuid }));
|
||||
extensionDescriptions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]);
|
||||
}
|
||||
|
||||
extensionDescriptions.sort((a, b) => {
|
||||
if (a.extensionFolderPath < b.extensionFolderPath) {
|
||||
return -1;
|
||||
48
src/vs/workbench/services/extensions/node/proxyIdentifier.ts
Normal file
48
src/vs/workbench/services/extensions/node/proxyIdentifier.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export interface IRPCProtocol {
|
||||
/**
|
||||
* Returns a proxy to an object addressable/named in the extension host process or in the renderer process.
|
||||
*/
|
||||
getProxy<T>(identifier: ProxyIdentifier<T>): T;
|
||||
|
||||
/**
|
||||
* Register manually created instance.
|
||||
*/
|
||||
set<T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R;
|
||||
|
||||
/**
|
||||
* Assert these identifiers are already registered via `.set`.
|
||||
*/
|
||||
assertRegistered(identifiers: ProxyIdentifier<any>[]): void;
|
||||
}
|
||||
|
||||
export class ProxyIdentifier<T> {
|
||||
_proxyIdentifierBrand: void;
|
||||
_suppressCompilerUnusedWarning: T;
|
||||
|
||||
public readonly isMain: boolean;
|
||||
public readonly id: string;
|
||||
|
||||
constructor(isMain: boolean, id: string) {
|
||||
this.isMain = isMain;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Using `isFancy` indicates that arguments or results of type `URI` or `RegExp`
|
||||
* will be serialized/deserialized automatically, but this has a performance cost,
|
||||
* as each argument/result must be visited.
|
||||
*/
|
||||
export function createMainContextProxyIdentifier<T>(identifier: string): ProxyIdentifier<T> {
|
||||
return new ProxyIdentifier(true, 'm' + identifier);
|
||||
}
|
||||
|
||||
export function createExtHostContextProxyIdentifier<T>(identifier: string): ProxyIdentifier<T> {
|
||||
return new ProxyIdentifier(false, 'e' + identifier);
|
||||
}
|
||||
@@ -5,19 +5,19 @@
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as marshalling from 'vs/base/common/marshalling';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { LazyPromise } from 'vs/workbench/services/extensions/node/lazyPromise';
|
||||
import { ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export interface IDispatcher {
|
||||
invoke(proxyId: string, methodName: string, args: any[]): any;
|
||||
}
|
||||
declare var Proxy: any; // TODO@TypeScript
|
||||
|
||||
export class RPCProtocol {
|
||||
export class RPCProtocol implements IRPCProtocol {
|
||||
|
||||
private _isDisposed: boolean;
|
||||
private _bigHandler: IDispatcher;
|
||||
private readonly _locals: { [id: string]: any; };
|
||||
private readonly _proxies: { [id: string]: any; };
|
||||
private _lastMessageId: number;
|
||||
private readonly _invokedHandlers: { [req: string]: TPromise<any>; };
|
||||
private readonly _pendingRPCReplies: { [msgId: string]: LazyPromise; };
|
||||
@@ -25,7 +25,8 @@ export class RPCProtocol {
|
||||
|
||||
constructor(protocol: IMessagePassingProtocol) {
|
||||
this._isDisposed = false;
|
||||
this._bigHandler = null;
|
||||
this._locals = Object.create(null);
|
||||
this._proxies = Object.create(null);
|
||||
this._lastMessageId = 0;
|
||||
this._invokedHandlers = Object.create(null);
|
||||
this._pendingRPCReplies = {};
|
||||
@@ -42,96 +43,151 @@ export class RPCProtocol {
|
||||
});
|
||||
}
|
||||
|
||||
public getProxy<T>(identifier: ProxyIdentifier<T>): T {
|
||||
if (!this._proxies[identifier.id]) {
|
||||
this._proxies[identifier.id] = this._createProxy(identifier.id);
|
||||
}
|
||||
return this._proxies[identifier.id];
|
||||
}
|
||||
|
||||
private _createProxy<T>(proxyId: string): T {
|
||||
let handler = {
|
||||
get: (target, name: string) => {
|
||||
if (!target[name] && name.charCodeAt(0) === CharCode.DollarSign) {
|
||||
target[name] = (...myArgs: any[]) => {
|
||||
return this._remoteCall(proxyId, name, myArgs);
|
||||
};
|
||||
}
|
||||
return target[name];
|
||||
}
|
||||
};
|
||||
return new Proxy(Object.create(null), handler);
|
||||
}
|
||||
|
||||
public set<T, R extends T>(identifier: ProxyIdentifier<T>, value: R): R {
|
||||
this._locals[identifier.id] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
public assertRegistered(identifiers: ProxyIdentifier<any>[]): void {
|
||||
for (let i = 0, len = identifiers.length; i < len; i++) {
|
||||
const identifier = identifiers[i];
|
||||
if (!this._locals[identifier.id]) {
|
||||
throw new Error(`Missing actor ${identifier.id} (isMain: ${identifier.isMain})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _receiveOneMessage(rawmsg: string): void {
|
||||
if (this._isDisposed) {
|
||||
console.warn('Received message after being shutdown: ', rawmsg);
|
||||
return;
|
||||
}
|
||||
let msg = marshalling.parse(rawmsg);
|
||||
|
||||
if (msg.seq) {
|
||||
if (!this._pendingRPCReplies.hasOwnProperty(msg.seq)) {
|
||||
console.warn('Got reply to unknown seq');
|
||||
return;
|
||||
}
|
||||
let reply = this._pendingRPCReplies[msg.seq];
|
||||
delete this._pendingRPCReplies[msg.seq];
|
||||
|
||||
if (msg.err) {
|
||||
let err = msg.err;
|
||||
if (msg.err.$isError) {
|
||||
err = new Error();
|
||||
err.name = msg.err.name;
|
||||
err.message = msg.err.message;
|
||||
err.stack = msg.err.stack;
|
||||
}
|
||||
reply.resolveErr(err);
|
||||
return;
|
||||
}
|
||||
|
||||
reply.resolveOk(msg.res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.cancel) {
|
||||
if (this._invokedHandlers[msg.cancel]) {
|
||||
this._invokedHandlers[msg.cancel].cancel();
|
||||
}
|
||||
return;
|
||||
let msg = <RPCMessage>JSON.parse(rawmsg);
|
||||
|
||||
switch (msg.type) {
|
||||
case MessageType.Request:
|
||||
this._receiveRequest(msg);
|
||||
break;
|
||||
case MessageType.Cancel:
|
||||
this._receiveCancel(msg);
|
||||
break;
|
||||
case MessageType.Reply:
|
||||
this._receiveReply(msg);
|
||||
break;
|
||||
case MessageType.ReplyErr:
|
||||
this._receiveReplyErr(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.err) {
|
||||
console.error(msg.err);
|
||||
return;
|
||||
}
|
||||
private _receiveRequest(msg: RequestMessage): void {
|
||||
const callId = msg.id;
|
||||
const proxyId = msg.proxyId;
|
||||
|
||||
let rpcId = msg.rpcId;
|
||||
this._invokedHandlers[callId] = this._invokeHandler(proxyId, msg.method, msg.args);
|
||||
|
||||
if (!this._bigHandler) {
|
||||
throw new Error('got message before big handler attached!');
|
||||
}
|
||||
|
||||
let req = msg.req;
|
||||
|
||||
this._invokedHandlers[req] = this._invokeHandler(rpcId, msg.method, msg.args);
|
||||
|
||||
this._invokedHandlers[req].then((r) => {
|
||||
delete this._invokedHandlers[req];
|
||||
this._multiplexor.send(MessageFactory.replyOK(req, r));
|
||||
this._invokedHandlers[callId].then((r) => {
|
||||
delete this._invokedHandlers[callId];
|
||||
this._multiplexor.send(MessageFactory.replyOK(callId, r));
|
||||
}, (err) => {
|
||||
delete this._invokedHandlers[req];
|
||||
this._multiplexor.send(MessageFactory.replyErr(req, err));
|
||||
delete this._invokedHandlers[callId];
|
||||
this._multiplexor.send(MessageFactory.replyErr(callId, err));
|
||||
});
|
||||
}
|
||||
|
||||
private _receiveCancel(msg: CancelMessage): void {
|
||||
const callId = msg.id;
|
||||
if (this._invokedHandlers[callId]) {
|
||||
this._invokedHandlers[callId].cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private _receiveReply(msg: ReplyMessage): void {
|
||||
const callId = msg.id;
|
||||
if (!this._pendingRPCReplies.hasOwnProperty(callId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingReply = this._pendingRPCReplies[callId];
|
||||
delete this._pendingRPCReplies[callId];
|
||||
|
||||
pendingReply.resolveOk(msg.res);
|
||||
}
|
||||
|
||||
private _receiveReplyErr(msg: ReplyErrMessage): void {
|
||||
const callId = msg.id;
|
||||
if (!this._pendingRPCReplies.hasOwnProperty(callId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingReply = this._pendingRPCReplies[callId];
|
||||
delete this._pendingRPCReplies[callId];
|
||||
|
||||
let err: Error = null;
|
||||
if (msg.err && msg.err.$isError) {
|
||||
err = new Error();
|
||||
err.name = msg.err.name;
|
||||
err.message = msg.err.message;
|
||||
err.stack = msg.err.stack;
|
||||
}
|
||||
pendingReply.resolveErr(err);
|
||||
}
|
||||
|
||||
private _invokeHandler(proxyId: string, methodName: string, args: any[]): TPromise<any> {
|
||||
try {
|
||||
return TPromise.as(this._bigHandler.invoke(proxyId, methodName, args));
|
||||
return TPromise.as(this._doInvokeHandler(proxyId, methodName, args));
|
||||
} catch (err) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
}
|
||||
|
||||
public callOnRemote(proxyId: string, methodName: string, args: any[]): TPromise<any> {
|
||||
private _doInvokeHandler(proxyId: string, methodName: string, args: any[]): any {
|
||||
if (!this._locals[proxyId]) {
|
||||
throw new Error('Unknown actor ' + proxyId);
|
||||
}
|
||||
let actor = this._locals[proxyId];
|
||||
let method = actor[methodName];
|
||||
if (typeof method !== 'function') {
|
||||
throw new Error('Unknown method ' + methodName + ' on actor ' + proxyId);
|
||||
}
|
||||
return method.apply(actor, args);
|
||||
}
|
||||
|
||||
private _remoteCall(proxyId: string, methodName: string, args: any[]): TPromise<any> {
|
||||
if (this._isDisposed) {
|
||||
return TPromise.wrapError<any>(errors.canceled());
|
||||
}
|
||||
|
||||
let req = String(++this._lastMessageId);
|
||||
let result = new LazyPromise(() => {
|
||||
this._multiplexor.send(MessageFactory.cancel(req));
|
||||
const callId = String(++this._lastMessageId);
|
||||
const result = new LazyPromise(() => {
|
||||
this._multiplexor.send(MessageFactory.cancel(callId));
|
||||
});
|
||||
|
||||
this._pendingRPCReplies[req] = result;
|
||||
|
||||
this._multiplexor.send(MessageFactory.request(req, proxyId, methodName, args));
|
||||
|
||||
this._pendingRPCReplies[callId] = result;
|
||||
this._multiplexor.send(MessageFactory.request(callId, proxyId, methodName, args));
|
||||
return result;
|
||||
}
|
||||
|
||||
public setDispatcher(handler: IDispatcher): void {
|
||||
this._bigHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,24 +231,55 @@ class RPCMultiplexer {
|
||||
|
||||
class MessageFactory {
|
||||
public static cancel(req: string): string {
|
||||
return `{"cancel":"${req}"}`;
|
||||
return `{"type":${MessageType.Cancel},"id":"${req}"}`;
|
||||
}
|
||||
|
||||
public static request(req: string, rpcId: string, method: string, args: any[]): string {
|
||||
return `{"req":"${req}","rpcId":"${rpcId}","method":"${method}","args":${marshalling.stringify(args)}}`;
|
||||
return `{"type":${MessageType.Request},"id":"${req}","proxyId":"${rpcId}","method":"${method}","args":${JSON.stringify(args)}}`;
|
||||
}
|
||||
|
||||
public static replyOK(req: string, res: any): string {
|
||||
if (typeof res === 'undefined') {
|
||||
return `{"seq":"${req}"}`;
|
||||
return `{"type":${MessageType.Reply},"id":"${req}"}`;
|
||||
}
|
||||
return `{"seq":"${req}","res":${marshalling.stringify(res)}}`;
|
||||
return `{"type":${MessageType.Reply},"id":"${req}","res":${JSON.stringify(res)}}`;
|
||||
}
|
||||
|
||||
public static replyErr(req: string, err: any): string {
|
||||
if (typeof err === 'undefined') {
|
||||
return `{"seq":"${req}","err":null}`;
|
||||
if (err instanceof Error) {
|
||||
return `{"type":${MessageType.ReplyErr},"id":"${req}","err":${JSON.stringify(errors.transformErrorForSerialization(err))}}`;
|
||||
}
|
||||
return `{"seq":"${req}","err":${marshalling.stringify(errors.transformErrorForSerialization(err))}}`;
|
||||
return `{"type":${MessageType.ReplyErr},"id":"${req}","err":null}`;
|
||||
}
|
||||
}
|
||||
|
||||
const enum MessageType {
|
||||
Request = 1,
|
||||
Cancel = 2,
|
||||
Reply = 3,
|
||||
ReplyErr = 4
|
||||
}
|
||||
|
||||
class RequestMessage {
|
||||
type: MessageType.Request;
|
||||
id: string;
|
||||
proxyId: string;
|
||||
method: string;
|
||||
args: any[];
|
||||
}
|
||||
class CancelMessage {
|
||||
type: MessageType.Cancel;
|
||||
id: string;
|
||||
}
|
||||
class ReplyMessage {
|
||||
type: MessageType.Reply;
|
||||
id: string;
|
||||
res: any;
|
||||
}
|
||||
class ReplyErrMessage {
|
||||
type: MessageType.ReplyErr;
|
||||
id: string;
|
||||
err: errors.SerializedError;
|
||||
}
|
||||
|
||||
type RPCMessage = RequestMessage | CancelMessage | ReplyMessage | ReplyErrMessage;
|
||||
|
||||
Reference in New Issue
Block a user