Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3 (#5587)

* Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3

* pipeline errors

* fix build
This commit is contained in:
Anthony Dresser
2019-05-23 11:16:03 -07:00
committed by GitHub
parent ca36f20c6b
commit cf8f8907ee
141 changed files with 6450 additions and 1228 deletions

View File

@@ -29,6 +29,7 @@ export class RemoteUserConfiguration extends Disposable {
private readonly _cachedConfiguration: CachedUserConfiguration;
private readonly _configurationFileService: IConfigurationFileService;
private _userConfiguration: UserConfiguration | CachedUserConfiguration;
private _userConfigurationInitializationPromise: Promise<ConfigurationModel> | null = null;
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
@@ -46,7 +47,8 @@ export class RemoteUserConfiguration extends Disposable {
if (environment) {
const userConfiguration = this._register(new UserConfiguration(environment.settingsPath, MACHINE_SCOPES, this._configurationFileService));
this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));
const configurationModel = await userConfiguration.initialize();
this._userConfigurationInitializationPromise = userConfiguration.initialize();
const configurationModel = await this._userConfigurationInitializationPromise;
this._userConfiguration.dispose();
this._userConfiguration = userConfiguration;
this.onDidUserConfigurationChange(configurationModel);
@@ -54,8 +56,20 @@ export class RemoteUserConfiguration extends Disposable {
});
}
initialize(): Promise<ConfigurationModel> {
return this._userConfiguration.initialize();
async initialize(): Promise<ConfigurationModel> {
if (this._userConfiguration instanceof UserConfiguration) {
return this._userConfiguration.initialize();
}
// Initialize cached configuration
let configurationModel = await this._userConfiguration.initialize();
if (this._userConfigurationInitializationPromise) {
// Use user configuration
configurationModel = await this._userConfigurationInitializationPromise;
this._userConfigurationInitializationPromise = null;
}
return configurationModel;
}
reload(): Promise<ConfigurationModel> {

View File

@@ -543,8 +543,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
private onRemoteUserConfigurationChanged(userConfiguration: ConfigurationModel): void {
const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration);
this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
if (this._configuration) {
const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration);
this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
}
}
private onWorkspaceConfigurationChanged(): Promise<void> {

View File

@@ -22,7 +22,7 @@ import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configurati
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices';
import { workbenchInstantiationService, TestTextFileService, RemoteFileSystemProvider } from 'vs/workbench/test/workbenchTestServices';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -42,6 +42,8 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider';
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration';
class SettingsTestEnvironmentService extends EnvironmentService {
@@ -95,6 +97,177 @@ suite('WorkspaceContextService - Folder', () => {
// {{SQL CARBON EDIT}} - Remove tests
assert.equal(0, 0);
});
test('configuration of newly added folder is available on configuration change event', async () => {
// {{SQL CARBON EDIT}} - Remove tests
assert.equal(0, 0);
});
});
suite('WorkspaceConfigurationService - Remote Folder', () => {
let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: WorkspaceService, globalSettingsFile: string, remoteSettingsFile: string, instantiationService: TestInstantiationService, resolveRemoteEnvironment: () => void;
const remoteAuthority = 'configuraiton-tests';
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
suiteSetup(() => {
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.remote.applicationSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
},
'configurationService.remote.machineSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE
},
'configurationService.remote.testSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.RESOURCE
}
}
});
});
setup(() => {
return setUpFolderWorkspace(workspaceName)
.then(({ parentDir, folderDir }) => {
parentResource = parentDir;
workspaceDir = folderDir;
globalSettingsFile = path.join(parentDir, 'settings.json');
remoteSettingsFile = path.join(parentDir, 'remote-settings.json');
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
const remoteEnvironmentPromise = new Promise<Partial<IRemoteAgentEnvironment>>(c => resolveRemoteEnvironment = () => c({ settingsPath: URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }) }));
const remoteAgentService = instantiationService.stub(IRemoteAgentService, <Partial<IRemoteAgentService>>{ getEnvironment: () => remoteEnvironmentPromise });
const fileService = new FileService(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
const configurationFileService = new ConfigurationFileService();
configurationFileService.fileService = fileService;
const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() };
testObject = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache, remoteAuthority }, configurationFileService, remoteAgentService);
instantiationService.stub(IWorkspaceContextService, testObject);
instantiationService.stub(IConfigurationService, testObject);
instantiationService.stub(IEnvironmentService, environmentService);
instantiationService.stub(IFileService, fileService);
});
});
async function initialize(): Promise<void> {
await testObject.initialize(convertToWorkspacePayload(URI.file(workspaceDir)));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
testObject.acquireInstantiationService(instantiationService);
}
function registerRemoteFileSystemProvider(): void {
instantiationService.get(IFileService).registerProvider(Schemas.vscodeRemote, new RemoteFileSystemProvider(diskFileSystemProvider, remoteAuthority));
}
function registerRemoteFileSystemProviderOnActivation(): void {
const disposable = instantiationService.get(IFileService).onWillActivateFileSystemProvider(e => {
if (e.scheme === Schemas.vscodeRemote) {
disposable.dispose();
e.join(Promise.resolve().then(() => registerRemoteFileSystemProvider()));
}
});
}
teardown(() => {
if (testObject) {
(<WorkspaceService>testObject).dispose();
}
if (parentResource) {
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
}
return undefined;
});
test('remote settings override globals', async () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
registerRemoteFileSystemProvider();
resolveRemoteEnvironment();
await initialize();
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
});
test('remote settings override globals after remote provider is registered on activation', async () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
resolveRemoteEnvironment();
registerRemoteFileSystemProviderOnActivation();
await initialize();
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
});
test('remote settings override globals after remote environment is resolved', async () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
registerRemoteFileSystemProvider();
await initialize();
const promise = new Promise((c, e) => {
testObject.onDidChangeConfiguration(event => {
try {
assert.equal(event.source, ConfigurationTarget.USER);
assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
c();
} catch (error) {
e(error);
}
});
});
resolveRemoteEnvironment();
return promise;
});
test('remote settings override globals after remote provider is registered on activation and remote environment is resolved', async () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
registerRemoteFileSystemProviderOnActivation();
await initialize();
const promise = new Promise((c, e) => {
testObject.onDidChangeConfiguration(event => {
try {
assert.equal(event.source, ConfigurationTarget.USER);
assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
c();
} catch (error) {
e(error);
}
});
});
resolveRemoteEnvironment();
return promise;
});
// test('update remote settings', async () => {
// registerRemoteFileSystemProvider();
// resolveRemoteEnvironment();
// await initialize();
// assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet');
// const promise = new Promise((c, e) => {
// testObject.onDidChangeConfiguration(event => {
// try {
// assert.equal(event.source, ConfigurationTarget.USER);
// assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
// assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
// c();
// } catch (error) {
// e(error);
// }
// });
// });
// fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
// return promise;
// });
});
function getWorkspaceId(configPath: URI): string {

View File

@@ -279,9 +279,8 @@ export class RemoteFileDialog {
if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) {
this.filePickBox.validationMessage = undefined;
const filePickBoxUri = this.filePickBoxValue();
const valueUri = resources.removeTrailingPathSeparator(filePickBoxUri);
let updated: UpdateResult = UpdateResult.NotUpdated;
if (!resources.isEqual(resources.removeTrailingPathSeparator(this.currentFolder), valueUri, true)) {
if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) {
updated = await this.tryUpdateItems(value, filePickBoxUri);
}
if (updated === UpdateResult.NotUpdated) {
@@ -660,7 +659,8 @@ export class RemoteFileDialog {
if (force && trailing) {
// Keep the cursor position in front of the save as name.
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - trailing.length];
} else {
} else if (!trailing) {
// If there is trailing, we don't move the cursor. If there is no trailing, cursor goes at the end.
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
}
this.filePickBox.busy = false;

View File

@@ -16,6 +16,12 @@ import { URI } from 'vs/base/common/uri';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import pkg from 'vs/platform/product/node/package';
@@ -35,6 +41,7 @@ import { Schemas } from 'vs/base/common/network';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
@@ -64,6 +71,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: any;
private _remoteExtensionsEnvironmentData: Map<string, IRemoteAgentEnvironment>;
private readonly _extensionHostLogsLocation: URI;
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _installedExtensionsReady: Barrier;
@@ -102,6 +111,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
@IWindowService private readonly _windowService: IWindowService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IFileService fileService: IFileService
) {
@@ -112,7 +124,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
}));
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`));
this._remoteExtensionsEnvironmentData = new Map<string, IRemoteAgentEnvironment>();
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${_windowService.windowId}`));
this._registry = new ExtensionDescriptionRegistry([]);
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
@@ -425,6 +439,17 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
private _createProvider(remoteAuthority: string): IInitDataProvider {
return {
remoteAuthority: remoteAuthority,
getInitData: () => {
return this._installedExtensionsReady.wait().then(() => {
return this._remoteExtensionsEnvironmentData.get(remoteAuthority)!;
});
}
};
}
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
this._stopExtensionHostProcess();
@@ -436,52 +461,63 @@ export class ExtensionService extends Disposable implements IExtensionService {
} else {
// restart case
autoStart = true;
extensions = this.getExtensions();
extensions = this.getExtensions().then((extensions) => extensions.filter(ext => ext.extensionLocation.scheme === Schemas.file));
}
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.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, true));
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(extHostProcessManager);
const remoteAgentConnection = this._remoteAgentService.getConnection();
if (remoteAgentConnection) {
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority));
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
remoteExtHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, false));
remoteExtHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(remoteExtHostProcessManager);
}
}
private _onExtensionHostCrashed(code: number, signal: string | null): void {
private _onExtensionHostCrashed(code: number, signal: string | null, showNotification: boolean): void {
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
this._stopExtensionHostProcess();
if (code === 55) {
this._notificationService.prompt(
Severity.Error,
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
if (showNotification) {
if (code === 55) {
this._notificationService.prompt(
Severity.Error,
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
[{
label: nls.localize('relaunch', "Relaunch VS Code"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const windowsService = accessor.get(IWindowsService);
windowsService.relaunch({});
});
}
}]
);
return;
}
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
if (code === 87) {
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
}
this._notificationService.prompt(Severity.Error, message,
[{
label: nls.localize('relaunch', "Relaunch VS Code"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const windowsService = accessor.get(IWindowsService);
windowsService.relaunch({});
});
}
label: nls.localize('devTools', "Open Developer Tools"),
run: () => this._windowService.openDevTools()
},
{
label: nls.localize('restart', "Restart Extension Host"),
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
}]
);
return;
}
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
if (code === 87) {
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
}
this._notificationService.prompt(Severity.Error, message,
[{
label: nls.localize('devTools', "Open Developer Tools"),
run: () => this._windowService.openDevTools()
},
{
label: nls.localize('restart', "Restart Extension Host"),
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
}]
);
}
// ---- begin IExtensionService
@@ -590,12 +626,112 @@ export class ExtensionService extends Disposable implements IExtensionService {
});
}
private async _resolveAuthorityAgain(): Promise<void> {
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return;
}
const extensionHost = this._extensionHostProcessManagers[0];
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
try {
const resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
} catch (err) {
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
}
}
private async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions(this.createLogger());
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
const extensionHost = this._extensionHostProcessManagers[0];
const extensions = await this._extensionScanner.scannedExtensions;
const enabledExtensions = await this._getRuntimeExtensions(extensions);
let localExtensions = await this._extensionScanner.scannedExtensions;
if (remoteAuthority) {
let resolvedAuthority: ResolvedAuthority;
try {
resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
} catch (err) {
console.error(err);
const plusIndex = remoteAuthority.indexOf('+');
const authorityFriendlyName = plusIndex > 0 ? remoteAuthority.substr(0, plusIndex) : remoteAuthority;
if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) {
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", authorityFriendlyName) });
} else {
console.log(`Not showing a notification for the error`);
}
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
// Proceed with the local extension host
await this._startLocalExtensionHost(extensionHost, localExtensions);
return;
}
// set the resolved authority
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
// monitor for breakage
const connection = this._remoteAgentService.getConnection();
if (connection) {
connection.onDidStateChange(async (e) => {
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return;
}
if (e.type === PersistenConnectionEventType.ConnectionLost) {
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
}
});
connection.onReconnecting(() => this._resolveAuthorityAgain());
}
// fetch the remote environment
const remoteEnv = (await this._remoteAgentService.getEnvironment())!;
// revive URIs
remoteEnv.extensions.forEach((extension) => {
(<any>extension).extensionLocation = URI.revive(extension.extensionLocation);
});
// remove UI extensions from the remote extensions
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._configurationService));
// remove non-UI extensions from the local extensions
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._configurationService));
// in case of overlap, the remote wins
const isRemoteExtension = new Set<string>();
remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));
// compute enabled extensions
const enabledExtensions = await this._getRuntimeExtensions((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
// remove disabled extensions
const isEnabled = new Set<string>();
enabledExtensions.forEach(extension => isEnabled.add(ExtensionIdentifier.toKey(extension.identifier)));
remoteEnv.extensions = remoteEnv.extensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
// save for remote extension's init data
this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(localExtensions.map(extension => extension.identifier));
this._releaseBarrier();
} else {
await this._startLocalExtensionHost(extensionHost, localExtensions);
}
}
private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise<void> {
const enabledExtensions = await this._getRuntimeExtensions(localExtensions);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));

View File

@@ -0,0 +1,247 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer as ipc } from 'electron';
import { Emitter, Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/node/product';
import pkg from 'vs/platform/product/node/package';
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import * as platform from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { VSBuffer } from 'vs/base/common/buffer';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
export interface IInitDataProvider {
readonly remoteAuthority: string;
getInitData(): Promise<IRemoteAgentEnvironment>;
}
export class RemoteExtensionHostClient extends Disposable implements IExtensionHostStarter {
private _onCrashed: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
public readonly onCrashed: Event<[number, string | null]> = this._onCrashed.event;
private _protocol: PersistentProtocol | null;
private readonly _isExtensionDevHost: boolean;
private readonly _isExtensionDevTestFromCli: boolean;
private _terminating: boolean;
constructor(
private readonly _allExtensions: Promise<IExtensionDescription[]>,
private readonly _initDataProvider: IInitDataProvider,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IWindowService private readonly _windowService: IWindowService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService
) {
super();
this._protocol = null;
this._terminating = false;
this._register(this._lifecycleService.onShutdown(reason => this.dispose()));
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
}
public start(): Promise<IMessagePassingProtocol> {
const options: IConnectionOptions = {
isBuilt: this._environmentService.isBuilt,
commit: product.commit,
webSocketFactory: nodeWebSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
return { host, port };
}
}
};
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolvedAuthority) => {
const startParams: IRemoteExtensionHostStartParams = {
language: platform.language,
debugId: this._environmentService.debugExtensionHost.debugId,
break: this._environmentService.debugExtensionHost.break,
port: this._environmentService.debugExtensionHost.port,
};
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
let debugOk = true;
if (extDevLocs && extDevLocs.length > 0) {
// TODO@AW: handles only first path in array
if (extDevLocs[0].scheme === Schemas.file) {
debugOk = false;
}
}
if (!debugOk) {
startParams.break = false;
}
return connectRemoteAgentExtensionHost(options, startParams).then(result => {
let { protocol, debugPort } = result;
const isExtensionDevelopmentDebug = typeof debugPort === 'number';
if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) {
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);
}
protocol.onClose(() => {
this._onExtHostConnectionLost();
});
protocol.onSocketClose(() => {
if (this._isExtensionDevHost) {
this._onExtHostConnectionLost();
}
});
// 1) wait for the incoming `ready` event and send the initialization data.
// 2) wait for the incoming `initialized` event.
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
let handle = setTimeout(() => {
reject('timeout');
}, 60 * 1000);
const disposable = protocol.onMessage(msg => {
if (isMessageOfType(msg, MessageType.Ready)) {
// 1) Extension Host is ready to receive messages, initialize it
this._createExtHostInitData(isExtensionDevelopmentDebug).then(data => protocol.send(VSBuffer.fromString(JSON.stringify(data))));
return;
}
if (isMessageOfType(msg, MessageType.Initialized)) {
// 2) Extension Host is initialized
clearTimeout(handle);
// stop listening for messages here
disposable.dispose();
// release this promise
this._protocol = protocol;
resolve(protocol);
return;
}
console.error(`received unexpected message during handshake phase from the extension host: `, msg);
});
});
});
});
}
private _onExtHostConnectionLost(): void {
if (this._terminating) {
// Expected termination path (we asked the process to terminate)
return;
}
// Unexpected termination
if (!this._isExtensionDevHost) {
this._onCrashed.fire([0, null]);
}
// Expected development extension termination: When the extension host goes down we also shutdown the window
else if (!this._isExtensionDevTestFromCli) {
this._windowService.closeWindow();
}
// When CLI testing make sure to exit with proper exit code
else {
ipc.send('vscode:exit', 0);
}
}
private _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IInitData> {
return Promise.all([this._allExtensions, this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]).then(([allExtensions, telemetryInfo, remoteExtensionHostData]) => {
// Collect all identifiers for extension ids which can be considered "resolved"
const resolvedExtensions = allExtensions.filter(extension => !extension.main).map(extension => extension.identifier);
const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
const workspace = this._contextService.getWorkspace();
const r: IInitData = {
commit: product.commit,
version: pkg.version,
parentPid: remoteExtensionHostData.pid,
environment: {
isExtensionDevelopmentDebug,
appRoot: remoteExtensionHostData.appRoot,
appSettingsHome: remoteExtensionHostData.appSettingsHome,
appName: product.nameLong,
appUriScheme: product.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: remoteExtensionHostData.globalStorageHome,
userHome: remoteExtensionHostData.userHome
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
configuration: workspace.configuration,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
},
resolvedExtensions: resolvedExtensions,
hostExtensions: hostExtensions,
extensions: remoteExtensionHostData.extensions,
telemetryInfo,
logLevel: this._logService.getLevel(),
logsLocation: remoteExtensionHostData.extensionHostLogsPath,
autoStart: true,
remoteAuthority: this._initDataProvider.remoteAuthority,
};
return r;
});
}
getInspectPort(): number | undefined {
return undefined;
}
dispose(): void {
super.dispose();
this._terminating = true;
if (this._protocol) {
// Send the extension host a request to terminate itself
// (graceful termination)
const socket = this._protocol.getSocket();
this._protocol.send(createMessageOfType(MessageType.Terminate));
this._protocol.sendDisconnect();
this._protocol.dispose();
socket.end();
this._protocol = null;
}
}
}

View File

@@ -15,7 +15,7 @@ import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHo
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain';
import { VSBuffer } from 'vs/base/common/buffer';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { IURITransformer } from 'vs/base/common/uriIpc';
@@ -72,7 +72,7 @@ function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
};
}
const createLogService: ILogServiceFn = initData => createSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
const createLogService: ILogServiceFn = initData => createBufferSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
interface IRendererConnection {
protocol: IMessagePassingProtocol;

View File

@@ -18,6 +18,8 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
import { localize } from 'vs/nls';
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/map';
export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService {
@@ -145,7 +147,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
// Install only on remote server
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix);
// Install UI Dependencies on local server
await this.installUIDependencies(manifest);
await this.installUIDependenciesAndPackedExtensions(manifest);
return promise;
}
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
@@ -165,8 +167,8 @@ export class MultiExtensionManagementService extends Disposable implements IExte
}
// Install only on remote server
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
// Install UI Dependencies on local server
await this.installUIDependencies(manifest);
// Install UI dependencies and packed extensions on local server
await this.installUIDependenciesAndPackedExtensions(manifest);
return promise;
} else {
return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
@@ -175,18 +177,11 @@ export class MultiExtensionManagementService extends Disposable implements IExte
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
private async installUIDependencies(manifest: IExtensionManifest): Promise<void> {
if (manifest.extensionDependencies && manifest.extensionDependencies.length) {
const dependencies = await this.extensionGalleryService.loadAllDependencies(manifest.extensionDependencies.map(id => ({ id })), CancellationToken.None);
if (dependencies.length) {
await Promise.all(dependencies.map(async d => {
const manifest = await this.extensionGalleryService.getManifest(d, CancellationToken.None);
if (manifest && isUIExtension(manifest, this.configurationService)) {
await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d);
}
}));
}
}
private async installUIDependenciesAndPackedExtensions(manifest: IExtensionManifest): Promise<void> {
const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(manifest, CancellationToken.None);
const installed = await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getInstalled();
const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier)));
await Promise.all(toInstall.map(d => this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d)));
}
getExtensionsReport(): Promise<IReportedExtension[]> {
@@ -196,6 +191,49 @@ export class MultiExtensionManagementService extends Disposable implements IExte
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
}
private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
const result = new Map<string, IGalleryExtension>();
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
await this.getAllUIDependenciesAndPackedExtensionsRecursively(extensions, result, token);
return values(result);
}
private async getAllUIDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, token: CancellationToken): Promise<void> {
if (toGet.length === 0) {
return Promise.resolve();
}
const extensions = (await this.extensionGalleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage;
const manifests = await Promise.all(extensions.map(e => this.extensionGalleryService.getManifest(e, token)));
const uiExtensionsManifests: IExtensionManifest[] = [];
for (let idx = 0; idx < extensions.length; idx++) {
const extension = extensions[idx];
const manifest = manifests[idx];
if (manifest && isUIExtension(manifest, this.configurationService)) {
result.set(extension.identifier.id.toLowerCase(), extension);
uiExtensionsManifests.push(manifest);
}
}
toGet = [];
for (const uiExtensionManifest of uiExtensionsManifests) {
if (isNonEmptyArray(uiExtensionManifest.extensionDependencies)) {
for (const id of uiExtensionManifest.extensionDependencies) {
if (!result.has(id.toLowerCase())) {
toGet.push(id);
}
}
}
if (isNonEmptyArray(uiExtensionManifest.extensionPack)) {
for (const id of uiExtensionManifest.extensionPack) {
if (!result.has(id.toLowerCase())) {
toGet.push(id);
}
}
}
}
return this.getAllUIDependenciesAndPackedExtensionsRecursively(toGet, result, token);
}
}
registerSingleton(IExtensionManagementService, MultiExtensionManagementService);

View File

@@ -6,20 +6,25 @@
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { IProductService } from 'vs/platform/product/common/product';
import { browserWebSocketFactory } from 'vs/platform/remote/browser/browserWebSocketFactory';
export class RemoteAgentService extends AbstractRemoteAgentService {
private readonly _connection: IRemoteAgentConnection | null = null;
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IProductService productService: IProductService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super(environmentService);
const authority = document.location.host;
this._connection = this._register(new RemoteAgentConnection(authority, productService.commit, browserWebSocketFactory, environmentService, remoteAuthorityResolverService));
}
getConnection(): IRemoteAgentConnection | null {
return null;
return this._connection;
}
}

View File

@@ -3,16 +3,111 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as net from 'net';
import { Barrier } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import product from 'vs/platform/product/node/product';
import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort);
return tunnel.waitForReady();
}
class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
public readonly tunnelRemotePort: number;
public readonly tunnelLocalPort: number;
private readonly _options: IConnectionOptions;
private readonly _server: net.Server;
private readonly _barrier: Barrier;
private readonly _listeningListener: () => void;
private readonly _connectionListener: (socket: net.Socket) => void;
constructor(options: IConnectionOptions, tunnelRemotePort: number) {
super();
this._options = options;
this._server = net.createServer();
this._barrier = new Barrier();
this._listeningListener = () => this._barrier.open();
this._server.on('listening', this._listeningListener);
this._connectionListener = (socket) => this._onConnection(socket);
this._server.on('connection', this._connectionListener);
this.tunnelRemotePort = tunnelRemotePort;
this.tunnelLocalPort = (<net.AddressInfo>this._server.listen(0).address()).port;
}
public dispose(): void {
super.dispose();
this._server.removeListener('listening', this._listeningListener);
this._server.removeListener('connection', this._connectionListener);
this._server.close();
}
public async waitForReady(): Promise<this> {
await this._barrier.wait();
return this;
}
private async _onConnection(localSocket: net.Socket): Promise<void> {
// pause reading on the socket until we have a chance to forward its data
localSocket.pause();
const protocol = await connectRemoteAgentTunnel(this._options, this.tunnelRemotePort);
const remoteSocket = (<NodeSocket>protocol.getSocket()).socket;
const dataChunk = protocol.readEntireBuffer();
protocol.dispose();
if (dataChunk.byteLength > 0) {
localSocket.write(dataChunk.buffer);
}
localSocket.on('end', () => remoteSocket.end());
localSocket.on('close', () => remoteSocket.end());
remoteSocket.on('end', () => localSocket.end());
remoteSocket.on('close', () => localSocket.end());
localSocket.pipe(remoteSocket);
remoteSocket.pipe(localSocket);
}
}
export class TunnelService implements ITunnelService {
_serviceBrand: any;
public constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
}
openTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
return undefined;
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return undefined;
}
const options: IConnectionOptions = {
isBuilt: this.environmentService.isBuilt,
commit: product.commit,
webSocketFactory: nodeWebSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority);
return { host, port };
}
}
};
return createRemoteTunnel(options, remotePort);
}
}

View File

@@ -312,7 +312,7 @@ export class SearchService implements IRawSearchService {
// Pattern match on results
const results: IRawFileMatch[] = [];
const normalizedSearchValueLowercase = prepareQuery(searchValue).value;
const normalizedSearchValueLowercase = prepareQuery(searchValue).lowercase;
for (const entry of cachedEntries) {
// Check if this entry is a match for the search value