Merge from vscode cfbd1999769f4f08dce29629fb92fdc0fac53829

This commit is contained in:
ADS Merger
2020-08-06 07:08:52 +00:00
parent 9c67832880
commit 540046ba00
362 changed files with 7588 additions and 6584 deletions

View File

@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { AuthenticationSession, AuthenticationSessionsChangeEvent, AuthenticationProviderInformation } from 'vs/editor/common/modes';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -15,6 +15,9 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; }
export const IAuthenticationService = createDecorator<IAuthenticationService>('IAuthenticationService');
@@ -68,12 +71,17 @@ export interface SessionRequestInfo {
[scopes: string]: SessionRequest;
}
CommandsRegistry.registerCommand('workbench.getCodeExchangeProxyEndpoints', function (accessor, _) {
const environmentService = accessor.get(IWorkbenchEnvironmentService);
return environmentService.options?.codeExchangeProxyEndpoints;
});
export class AuthenticationService extends Disposable implements IAuthenticationService {
declare readonly _serviceBrand: undefined;
private _placeholderMenuItem: IDisposable | undefined;
private _noAccountsMenuItem: IDisposable | undefined;
private _signInRequestItems = new Map<string, SessionRequestInfo>();
private _badgeDisposable: IDisposable | undefined;
private _accountBadgeDisposable = this._register(new MutableDisposable());
private _authenticationProviders: Map<string, MainThreadAuthenticationProvider> = new Map<string, MainThreadAuthenticationProvider>();
@@ -203,10 +211,9 @@ export class AuthenticationService extends Disposable implements IAuthentication
});
if (changed) {
if (this._signInRequestItems.size === 0) {
this._badgeDisposable?.dispose();
this._badgeDisposable = undefined;
} else {
this._accountBadgeDisposable.clear();
if (this._signInRequestItems.size > 0) {
let numberOfRequests = 0;
this._signInRequestItems.forEach(providerRequests => {
Object.keys(providerRequests).forEach(request => {
@@ -215,7 +222,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
});
const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested"));
this._badgeDisposable = this.activityService.showAccountsActivity({ badge });
this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });
}
}
}
@@ -284,6 +291,8 @@ export class AuthenticationService extends Disposable implements IAuthentication
});
}
this._accountBadgeDisposable.clear();
let numberOfRequests = 0;
this._signInRequestItems.forEach(providerRequests => {
Object.keys(providerRequests).forEach(request => {
@@ -292,7 +301,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
});
const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested"));
this._badgeDisposable = this.activityService.showAccountsActivity({ badge });
this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });
}
}
getLabel(id: string): string {

View File

@@ -99,7 +99,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
configurationRegistry.registerDefaultConfigurations([environmentService.options.configurationDefaults]);
}
this._register(configurationRegistry.onDidSchemaChange(e => this.registerConfigurationSchemas()));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
this._register(configurationRegistry.onDidUpdateConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
this.workspaceEditingQueue = new Queue<void>();
}
@@ -510,6 +510,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
const folderConfiguration = this.cachedFolderConfigs.get(this.workspace.folders[0].uri);
if (folderConfiguration) {
this._configuration.updateWorkspaceConfiguration(folderConfiguration.reprocess());
this._configuration.updateFolderConfiguration(this.workspace.folders[0].uri, folderConfiguration.reprocess());
}
} else {
this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings());

View File

@@ -112,7 +112,7 @@ suite.skip('WorkspaceContextService - Folder', () => { // {{SQL CARBON EDIT}} sk
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, new DiskFileSystemProvider(new NullLogService()), environmentService, new NullLogService()));
workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService(environmentService, new RemoteAuthorityResolverService(), new SignService(undefined), new NullLogService(), { _serviceBrand: undefined, ...product }));
workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService(environmentService, { _serviceBrand: undefined, ...product }, new RemoteAuthorityResolverService(), new SignService(undefined), new NullLogService()));
return (<WorkspaceService>workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir)));
});
});
@@ -882,56 +882,140 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI
});
});
test('application settings are not read from workspace', () => {
test('application settings are not read from workspace', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.applicationSetting": "userValue" }');
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.applicationSetting": "workspaceValue" }');
return testObject.reloadConfiguration()
.then(() => assert.equal(testObject.getValue('configurationService.folder.applicationSetting'), 'userValue'));
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.applicationSetting'), 'userValue');
});
test('machine settings are not read from workspace', () => {
test('application settings are not read from workspace when workspace folder uri is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.applicationSetting": "userValue" }');
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.applicationSetting": "workspaceValue" }');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.applicationSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue');
});
test('machine settings are not read from workspace', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.machineSetting": "userValue" }');
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.machineSetting": "workspaceValue" }');
return testObject.reloadConfiguration()
.then(() => assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'userValue'));
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue');
});
test('get application scope settings are not loaded after defaults are registered', () => {
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.anotherApplicationSetting": "workspaceValue" }');
return testObject.reloadConfiguration()
.then(() => {
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.folder.anotherApplicationSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
}
}
});
assert.deepEqual(testObject.keys().workspace, []);
});
test('machine settings are not read from workspace when workspace folder uri is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.machineSetting": "userValue" }');
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.machineSetting": "workspaceValue" }');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue');
});
test('get machine scope settings are not loaded after defaults are registered', () => {
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.anotherMachineSetting": "workspaceValue" }');
return testObject.reloadConfiguration()
.then(() => {
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.folder.anotherMachineSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE
}
}
});
assert.deepEqual(testObject.keys().workspace, []);
});
test('get application scope settings are not loaded after defaults are registered', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.applicationSetting-2": "userValue" }');
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.applicationSetting-2": "workspaceValue" }');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.applicationSetting-2'), 'workspaceValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.folder.applicationSetting-2': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
}
}
});
assert.equal(testObject.getValue('configurationService.folder.applicationSetting-2'), 'userValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.applicationSetting-2'), 'userValue');
});
test('get application scope settings are not loaded after defaults are registered when workspace folder uri is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.applicationSetting-3": "userValue" }');
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.applicationSetting-3": "workspaceValue" }');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'workspaceValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.folder.applicationSetting-3': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
}
}
});
assert.equal(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.applicationSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue');
});
test('get machine scope settings are not loaded after defaults are registered', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.machineSetting-2": "userValue" }');
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.machineSetting-2": "workspaceValue" }');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.machineSetting-2'), 'workspaceValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.folder.machineSetting-2': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE
}
}
});
assert.equal(testObject.getValue('configurationService.folder.machineSetting-2'), 'userValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.machineSetting-2'), 'userValue');
});
test('get machine scope settings are not loaded after defaults are registered when workspace folder uri is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.machineSetting-3": "userValue" }');
fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.machineSetting-3": "workspaceValue" }');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'workspaceValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.folder.machineSetting-3': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE
}
}
});
assert.equal(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue');
});
test('reload configuration emits events after global configuraiton changes', () => {
@@ -1231,111 +1315,202 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED
return undefined;
});
test('application settings are not read from workspace', () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.applicationSetting": "userValue" }');
return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.applicationSetting': 'workspaceValue' } }], true)
.then(() => testObject.reloadConfiguration())
.then(() => assert.equal(testObject.getValue('configurationService.workspace.applicationSetting'), 'userValue'));
test('application settings are not read from workspace', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.applicationSetting": "userValue" }');
await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.applicationSetting': 'workspaceValue' } }], true);
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.applicationSetting'), 'userValue');
});
test('machine settings are not read from workspace', () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.machineSetting": "userValue" }');
return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.machineSetting': 'workspaceValue' } }], true)
.then(() => testObject.reloadConfiguration())
.then(() => assert.equal(testObject.getValue('configurationService.workspace.machineSetting'), 'userValue'));
test('application settings are not read from workspace when folder is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.applicationSetting": "userValue" }');
await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.applicationSetting': 'workspaceValue' } }], true);
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.applicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
test('workspace settings override user settings after defaults are registered ', () => {
test('machine settings are not read from workspace', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.machineSetting": "userValue" }');
await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.machineSetting': 'workspaceValue' } }], true);
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'userValue');
});
test('machine settings are not read from workspace when folder is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.machineSetting": "userValue" }');
await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.machineSetting': 'workspaceValue' } }], true);
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.folder.machineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
test('get application scope settings are not loaded after defaults are registered', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.newSetting": "userValue" }');
return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.newSetting': 'workspaceValue' } }], true)
.then(() => testObject.reloadConfiguration())
.then(() => {
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.newSetting': {
'type': 'string',
'default': 'isSet'
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.newSetting'), 'workspaceValue');
});
await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.newSetting': 'workspaceValue' } }], true);
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.newSetting'), 'workspaceValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.newSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.newSetting'), 'userValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.newSetting'), 'userValue');
});
test('workspace settings override user settings after defaults are registered for machine overridable settings ', () => {
test('get application scope settings are not loaded after defaults are registered when workspace folder is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.newSetting-2": "userValue" }');
await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.newSetting-2': 'workspaceValue' } }], true);
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.newSetting-2': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.newSetting-2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
test('workspace settings override user settings after defaults are registered for machine overridable settings ', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.newMachineOverridableSetting": "userValue" }');
return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.newMachineOverridableSetting': 'workspaceValue' } }], true)
.then(() => testObject.reloadConfiguration())
.then(() => {
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.newMachineOverridableSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE_OVERRIDABLE
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue');
});
await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ path: ['settings'], value: { 'configurationService.workspace.newMachineOverridableSetting': 'workspaceValue' } }], true);
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.newMachineOverridableSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE_OVERRIDABLE
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.newMachineOverridableSetting'), 'workspaceValue');
});
test('application settings are not read from workspace folder', () => {
test('application settings are not read from workspace folder', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.applicationSetting": "userValue" }');
fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.applicationSetting": "workspaceFolderValue" }');
return testObject.reloadConfiguration()
.then(() => assert.equal(testObject.getValue('configurationService.workspace.applicationSetting'), 'userValue'));
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.applicationSetting'), 'userValue');
});
test('machine settings are not read from workspace folder', () => {
test('application settings are not read from workspace folder when workspace folder is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.applicationSetting": "userValue" }');
fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.applicationSetting": "workspaceFolderValue" }');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.applicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
test('machine settings are not read from workspace folder', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.machineSetting": "userValue" }');
fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.machineSetting": "workspaceFolderValue" }');
return testObject.reloadConfiguration()
.then(() => assert.equal(testObject.getValue('configurationService.workspace.machineSetting'), 'userValue'));
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.machineSetting'), 'userValue');
});
test('application settings are not read from workspace folder after defaults are registered', () => {
test('machine settings are not read from workspace folder when workspace folder is passed', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.machineSetting": "userValue" }');
fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.machineSetting": "workspaceFolderValue" }');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.machineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
test('application settings are not read from workspace folder after defaults are registered', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.testNewApplicationSetting": "userValue" }');
fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewApplicationSetting": "workspaceFolderValue" }');
return testObject.reloadConfiguration()
.then(() => {
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.testNewApplicationSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.testNewApplicationSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.APPLICATION
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.testNewApplicationSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
test('application settings are not read from workspace folder after defaults are registered', () => {
test('application settings are not read from workspace folder after defaults are registered', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.testNewMachineSetting": "userValue" }');
fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewMachineSetting": "workspaceFolderValue" }');
return testObject.reloadConfiguration()
.then(() => {
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.testNewMachineSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue');
configurationRegistry.registerConfiguration({
'id': '_test',
'type': 'object',
'properties': {
'configurationService.workspace.testNewMachineSetting': {
'type': 'string',
'default': 'isSet',
scope: ConfigurationScope.MACHINE
}
}
});
assert.equal(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
await testObject.reloadConfiguration();
assert.equal(testObject.getValue('configurationService.workspace.testNewMachineSetting', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'userValue');
});
test('resource setting in folder is read after it is registered later', () => {

View File

@@ -17,8 +17,8 @@ export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmen
readonly configuration: INativeEnvironmentConfiguration;
readonly disableCrashReporter: boolean;
readonly crashReporterDirectory?: string;
readonly crashReporterId?: string;
readonly cliPath: string;

View File

@@ -12,11 +12,11 @@ import { URI } from 'vs/base/common/uri';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
interface IScannedBuiltinExtension {
extensionPath: string,
packageJSON: IExtensionManifest,
packageNLSPath?: string,
readmePath?: string,
changelogPath?: string,
extensionPath: string;
packageJSON: IExtensionManifest;
packageNLS?: any;
readmePath?: string;
changelogPath?: string;
}
export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScannerService {
@@ -54,7 +54,7 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne
location: uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.extensionPath),
type: ExtensionType.System,
packageJSON: e.packageJSON,
packageNLSUrl: e.packageNLSPath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.packageNLSPath) : undefined,
packageNLS: e.packageNLS,
readmeUrl: e.readmePath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.readmePath) : undefined,
changelogUrl: e.changelogPath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.changelogPath) : undefined,
}));

View File

@@ -6,7 +6,7 @@
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { IExtension, IScannedExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IExtension, IScannedExtension, ExtensionType, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IStringDictionary } from 'vs/base/common/collections';
@@ -128,6 +128,7 @@ export interface IExtensionRecommendationsService {
getAllRecommendationsWithReason(): IStringDictionary<IExtensionRecommendationReson>;
getFileBasedRecommendations(): IExtensionRecommendation[];
getImportantRecommendations(): Promise<IExtensionRecommendation[]>;
getConfigBasedRecommendations(): Promise<IExtensionRecommendation[]>;
getOtherRecommendations(): Promise<IExtensionRecommendation[]>;
getWorkspaceRecommendations(): Promise<IExtensionRecommendation[]>;
@@ -145,6 +146,7 @@ export const IWebExtensionsScannerService = createDecorator<IWebExtensionsScanne
export interface IWebExtensionsScannerService {
readonly _serviceBrand: undefined;
scanExtensions(type?: ExtensionType): Promise<IScannedExtension[]>;
scanAndTranslateExtensions(type?: ExtensionType): Promise<ITranslatedScannedExtension[]>;
addExtension(galleryExtension: IGalleryExtension): Promise<IScannedExtension>;
removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void>;
}

View File

@@ -3,13 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionType, IExtensionIdentifier, IExtensionManifest, IScannedExtension } from 'vs/platform/extensions/common/extensions';
import { ExtensionType, IExtensionIdentifier, IExtensionManifest, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IGalleryExtension, IReportedExtension, IGalleryMetadata, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IRequestService, isSuccess, asText } from 'vs/platform/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
@@ -33,14 +30,13 @@ export class WebExtensionManagementService extends Disposable implements IExtens
constructor(
@IWebExtensionsScannerService private readonly webExtensionsScannerService: IWebExtensionsScannerService,
@IRequestService private readonly requestService: IRequestService,
@ILogService private readonly logService: ILogService,
) {
super();
}
async getInstalled(type?: ExtensionType): Promise<ILocalExtension[]> {
const extensions = await this.webExtensionsScannerService.scanExtensions(type);
const extensions = await this.webExtensionsScannerService.scanAndTranslateExtensions(type);
return Promise.all(extensions.map(e => this.toLocalExtension(e)));
}
@@ -83,23 +79,11 @@ export class WebExtensionManagementService extends Disposable implements IExtens
return userExtensions.find(e => areSameExtensions(e.identifier, identifier));
}
private async toLocalExtension(scannedExtension: IScannedExtension): Promise<ILocalExtension> {
let manifest = scannedExtension.packageJSON;
if (scannedExtension.packageNLSUrl) {
try {
const context = await this.requestService.request({ type: 'GET', url: scannedExtension.packageNLSUrl.toString() }, CancellationToken.None);
if (isSuccess(context)) {
const content = await asText(context);
if (content) {
manifest = localizeManifest(manifest, JSON.parse(content));
}
}
} catch (error) { /* ignore */ }
}
private async toLocalExtension(scannedExtension: ITranslatedScannedExtension): Promise<ILocalExtension> {
return <ILocalExtension>{
type: scannedExtension.type,
identifier: scannedExtension.identifier,
manifest,
manifest: scannedExtension.packageJSON,
location: scannedExtension.location,
isMachineScoped: false,
publisherId: null,

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as semver from 'semver-umd';
import { IBuiltinExtensionsScannerService, IScannedExtension, ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IBuiltinExtensionsScannerService, IScannedExtension, ExtensionType, IExtensionIdentifier, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { isWeb } from 'vs/base/common/platform';
@@ -21,6 +21,9 @@ import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extens
import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStaticExtension } from 'vs/workbench/workbench.web.api';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
interface IUserExtension {
identifier: IExtensionIdentifier;
@@ -44,7 +47,7 @@ const AssetTypeWebResource = 'Microsoft.VisualStudio.Code.WebResources';
function getExtensionLocation(assetUri: URI): URI { return joinPath(assetUri, AssetTypeWebResource, 'extension'); }
export class WebExtensionsScannerService implements IWebExtensionsScannerService {
export class WebExtensionsScannerService extends Disposable implements IWebExtensionsScannerService {
declare readonly _serviceBrand: undefined;
@@ -53,6 +56,8 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
private readonly extensionsResource: URI | undefined = undefined;
private readonly userExtensionsResourceLimiter: Queue<IUserExtension[]> = new Queue<IUserExtension[]>();
private userExtensionsPromise: Promise<IScannedExtension[]> | undefined;
constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IBuiltinExtensionsScannerService private readonly builtinExtensionsScannerService: IBuiltinExtensionsScannerService,
@@ -61,22 +66,48 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
@ILogService private readonly logService: ILogService,
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
super();
if (isWeb) {
this.extensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json');
this.systemExtensionsPromise = this.builtinExtensionsScannerService.scanBuiltinExtensions();
this.systemExtensionsPromise = this.readSystemExtensions();
this.defaultExtensionsPromise = this.readDefaultExtensions();
if (this.extensionsResource) {
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.extensionsResource!))(() => this.userExtensionsPromise = undefined));
}
}
}
private async readDefaultExtensions(): Promise<IScannedExtension[]> {
private async readSystemExtensions(): Promise<IScannedExtension[]> {
const extensions = await this.builtinExtensionsScannerService.scanBuiltinExtensions();
return extensions.concat(this.getStaticExtensions(true));
}
/**
* All extensions defined via `staticExtensions`
*/
private getStaticExtensions(builtin: boolean): IScannedExtension[] {
const staticExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : [];
return (
staticExtensions
.filter(e => Boolean(e.isBuiltin) === builtin)
.map(e => ({
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
location: e.extensionLocation,
type: ExtensionType.System,
packageJSON: e.packageJSON,
}))
);
}
private async readDefaultExtensions(): Promise<IScannedExtension[]> {
const defaultUserWebExtensions = await this.readDefaultUserWebExtensions();
return [...staticExtensions, ...defaultUserWebExtensions].map<IScannedExtension>(e => ({
const extensions = defaultUserWebExtensions.map(e => ({
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
location: e.extensionLocation,
type: ExtensionType.User,
packageJSON: e.packageJSON,
}));
return extensions.concat(this.getStaticExtensions(false));
}
private async readDefaultUserWebExtensions(): Promise<IStaticExtension[]> {
@@ -113,12 +144,52 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
if (type === undefined || type === ExtensionType.User) {
const staticExtensions = await this.defaultExtensionsPromise;
extensions.push(...staticExtensions);
const userExtensions = await this.scanUserExtensions();
if (!this.userExtensionsPromise) {
this.userExtensionsPromise = this.scanUserExtensions();
}
const userExtensions = await this.userExtensionsPromise;
extensions.push(...userExtensions);
}
return extensions;
}
async scanAndTranslateExtensions(type?: ExtensionType): Promise<ITranslatedScannedExtension[]> {
const extensions = await this.scanExtensions(type);
return Promise.all(extensions.map((ext) => this._translateScannedExtension(ext)));
}
private async _translateScannedExtension(scannedExtension: IScannedExtension): Promise<ITranslatedScannedExtension> {
let manifest = scannedExtension.packageJSON;
if (scannedExtension.packageNLS) {
// package.nls.json is inlined
try {
manifest = localizeManifest(manifest, scannedExtension.packageNLS);
} catch (error) {
console.log(error);
/* ignore */
}
} else if (scannedExtension.packageNLSUrl) {
// package.nls.json needs to be fetched
try {
const context = await this.requestService.request({ type: 'GET', url: scannedExtension.packageNLSUrl.toString() }, CancellationToken.None);
if (isSuccess(context)) {
const content = await asText(context);
if (content) {
manifest = localizeManifest(manifest, JSON.parse(content));
}
}
} catch (error) { /* ignore */ }
}
return {
identifier: scannedExtension.identifier,
location: scannedExtension.location,
type: scannedExtension.type,
packageJSON: manifest,
readmeUrl: scannedExtension.readmeUrl,
changelogUrl: scannedExtension.changelogUrl
};
}
async addExtension(galleryExtension: IGalleryExtension): Promise<IScannedExtension> {
if (!galleryExtension.assetTypes.some(type => type.startsWith(AssetTypeWebResource))) {
throw new Error(`Missing ${AssetTypeWebResource} asset type`);
@@ -149,7 +220,7 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
async removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void> {
let userExtensions = await this.readUserExtensions();
userExtensions = userExtensions.filter(extension => !(areSameExtensions(extension.identifier, identifier) && version ? extension.version === version : true));
userExtensions = userExtensions.filter(extension => !(areSameExtensions(extension.identifier, identifier) && (version ? extension.version === version : true)));
await this.writeUserExtensions(userExtensions);
}
@@ -226,6 +297,7 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
packageNLSUri: e.packageNLSUri?.toJSON(),
}));
await this.fileService.writeFile(this.extensionsResource!, VSBuffer.fromString(JSON.stringify(storedUserExtensions)));
this.userExtensionsPromise = undefined;
return userExtensions;
});
}

View File

@@ -397,7 +397,7 @@ suite('ExtensionEnablementService Test', () => {
assert.ok(!testObject.canChangeEnablement(extension));
});
test('test extension is disabled when disabled in enviroment', async () => {
test('test extension is disabled when disabled in environment', async () => {
const extension = aLocalExtension('pub.a');
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService);
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => Promise.resolve([extension, aLocalExtension('pub.b')]) } as IExtensionManagementService);

View File

@@ -17,19 +17,19 @@ import { AbstractExtensionService, parseScannedExtension } from 'vs/workbench/se
import { RemoteExtensionHost, IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
import { canExecuteOnWeb } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { DeltaExtensionsResult } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
private _disposables = new DisposableStore();
private _remoteInitData: IRemoteExtensionHostInitData | null = null;
private _runningLocation: Map<string, ExtensionRunningLocation>;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@@ -54,6 +54,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
productService,
);
this._runningLocation = new Map<string, ExtensionRunningLocation>();
this._initialize();
this._initFetchFileSystem();
}
@@ -73,10 +75,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return {
getInitData: async () => {
const allExtensions = await this.getExtensions();
const webExtensions = allExtensions.filter(ext => canExecuteOnWeb(ext, this._productService, this._configService));
const localWebWorkerExtensions = filterByRunningLocation(allExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker);
return {
autoStart: true,
extensions: webExtensions
extensions: localWebWorkerExtensions
};
}
};
@@ -109,32 +111,26 @@ export class ExtensionService extends AbstractExtensionService implements IExten
protected async _scanAndHandleExtensions(): Promise<void> {
// fetch the remote environment
let [remoteEnv, localExtensions] = await Promise.all([
let [localExtensions, remoteEnv, remoteExtensions] = await Promise.all([
this._webExtensionsScannerService.scanAndTranslateExtensions().then(extensions => extensions.map(parseScannedExtension)),
this._remoteAgentService.getEnvironment(),
this._webExtensionsScannerService.scanExtensions().then(extensions => extensions.map(parseScannedExtension))
this._remoteAgentService.scanExtensions()
]);
localExtensions = this._checkEnabledAndProposedAPI(localExtensions);
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions);
const remoteAgentConnection = this._remoteAgentService.getConnection();
this._runningLocation = _determineRunningLocation(this._productService, this._configService, localExtensions, remoteExtensions, Boolean(remoteEnv && remoteAgentConnection));
let result: DeltaExtensionsResult;
localExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker);
remoteExtensions = filterByRunningLocation(remoteExtensions, this._runningLocation, ExtensionRunningLocation.Remote);
// local: only enabled and web'ish extension
localExtensions = localExtensions!.filter(ext => this._isEnabled(ext) && canExecuteOnWeb(ext, this._productService, this._configService));
this._checkEnableProposedApi(localExtensions);
if (!remoteEnv || !remoteAgentConnection) {
result = this._registry.deltaExtensions(localExtensions, []);
} else {
// remote: only enabled and none-web'ish extension
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !canExecuteOnWeb(extension, this._productService, this._configService));
this._checkEnableProposedApi(remoteEnv.extensions);
// 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)));
const result = this._registry.deltaExtensions(remoteExtensions.concat(localExtensions), []);
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
if (remoteEnv && remoteAgentConnection) {
// save for remote extension's init data
this._remoteInitData = {
connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAgentConnection.remoteAuthority),
@@ -143,16 +139,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten
extensionHostLogsPath: remoteEnv.extensionHostLogsPath,
globalStorageHome: remoteEnv.globalStorageHome,
workspaceStorageHome: remoteEnv.workspaceStorageHome,
extensions: remoteEnv.extensions,
allExtensions: remoteEnv.extensions.concat(localExtensions)
extensions: remoteExtensions,
allExtensions: this._registry.getAllExtensionDescriptions()
};
result = this._registry.deltaExtensions(remoteEnv.extensions.concat(localExtensions), []);
}
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions());
}
@@ -164,4 +155,55 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
}
const enum ExtensionRunningLocation {
None,
LocalWebWorker,
Remote
}
export function determineRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], allExtensionKinds: Map<string, ExtensionKind[]>, hasRemote: boolean): Map<string, ExtensionRunningLocation> {
const localExtensionsSet = new Set<string>();
localExtensions.forEach(ext => localExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
const remoteExtensionsSet = new Set<string>();
remoteExtensions.forEach(ext => remoteExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
const pickRunningLocation = (extension: IExtensionDescription): ExtensionRunningLocation => {
const isInstalledLocally = localExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
const isInstalledRemotely = remoteExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
const extensionKinds = allExtensionKinds.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
for (const extensionKind of extensionKinds) {
if (extensionKind === 'ui' && isInstalledRemotely) {
// ui extensions run remotely if possible
return ExtensionRunningLocation.Remote;
}
if (extensionKind === 'workspace' && isInstalledRemotely) {
// workspace extensions run remotely if possible
return ExtensionRunningLocation.Remote;
}
if (extensionKind === 'web' && isInstalledLocally) {
// web worker extensions run in the local web worker if possible
return ExtensionRunningLocation.LocalWebWorker;
}
}
return ExtensionRunningLocation.None;
};
const runningLocation = new Map<string, ExtensionRunningLocation>();
localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext)));
remoteExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext)));
return runningLocation;
}
function _determineRunningLocation(productService: IProductService, configurationService: IConfigurationService, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], hasRemote: boolean): Map<string, ExtensionRunningLocation> {
const allExtensionKinds = new Map<string, ExtensionKind[]>();
localExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), getExtensionKind(ext, productService, configurationService)));
remoteExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), getExtensionKind(ext, productService, configurationService)));
return determineRunningLocation(localExtensions, remoteExtensions, allExtensionKinds, hasRemote);
}
function filterByRunningLocation(extensions: IExtensionDescription[], runningLocation: Map<string, ExtensionRunningLocation>, desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] {
return extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === desiredRunningLocation);
}
registerSingleton(IExtensionService, ExtensionService);

View File

@@ -5,7 +5,7 @@
import { getWorkerBootstrapUrl } from 'vs/base/worker/defaultWorkerFactory';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { VSBuffer } from 'vs/base/common/buffer';
import { createMessageOfType, MessageType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
@@ -16,6 +16,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as platform from 'vs/base/common/platform';
import * as dom from 'vs/base/browser/dom';
import { URI } from 'vs/base/common/uri';
import { IExtensionHost, ExtensionHostLogFileName, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -24,6 +25,11 @@ import { joinPath } from 'vs/base/common/resources';
import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { localize } from 'vs/nls';
import { generateUuid } from 'vs/base/common/uuid';
import { canceled, onUnexpectedError } from 'vs/base/common/errors';
import { WEB_WORKER_IFRAME } from 'vs/workbench/services/extensions/common/webWorkerIframe';
const WRAP_IN_IFRAME = true;
export interface IWebWorkerExtensionHostInitData {
readonly autoStart: boolean;
@@ -34,17 +40,17 @@ export interface IWebWorkerExtensionHostDataProvider {
getInitData(): Promise<IWebWorkerExtensionHostInitData>;
}
export class WebWorkerExtensionHost implements IExtensionHost {
export class WebWorkerExtensionHost extends Disposable implements IExtensionHost {
public readonly kind = ExtensionHostKind.LocalWebWorker;
public readonly remoteAuthority = null;
private _toDispose = new DisposableStore();
private _isTerminating: boolean = false;
private _protocol?: IMessagePassingProtocol;
private readonly _onDidExit = this._register(new Emitter<[number, string | null]>());
public readonly onExit: Event<[number, string | null]> = this._onDidExit.event;
private readonly _onDidExit = new Emitter<[number, string | null]>();
readonly onExit: Event<[number, string | null]> = this._onDidExit.event;
private _isTerminating: boolean;
private _protocolPromise: Promise<IMessagePassingProtocol> | null;
private _protocol: IMessagePassingProtocol | null;
private readonly _extensionHostLogsLocation: URI;
private readonly _extensionHostLogFile: URI;
@@ -58,76 +64,168 @@ export class WebWorkerExtensionHost implements IExtensionHost {
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@IProductService private readonly _productService: IProductService,
) {
super();
this._isTerminating = false;
this._protocolPromise = null;
this._protocol = null;
this._extensionHostLogsLocation = URI.file(this._environmentService.logsPath).with({ scheme: this._environmentService.logFile.scheme });
this._extensionHostLogFile = joinPath(this._extensionHostLogsLocation, `${ExtensionHostLogFileName}.log`);
}
async start(): Promise<IMessagePassingProtocol> {
if (!this._protocol) {
const emitter = new Emitter<VSBuffer>();
const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost');
const worker = new Worker(url, { name: 'WorkerExtensionHost' });
worker.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
return;
}
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
};
worker.onerror = (event) => {
console.error(event.message, event.error);
this._onDidExit.fire([81, event.message || event.error]);
};
// keep for cleanup
this._toDispose.add(emitter);
this._toDispose.add(toDisposable(() => worker.terminate()));
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
worker.postMessage(data, [data]);
}
};
// extension host handshake happens below
// (1) <== wait for: Ready
// (2) ==> send: init data
// (3) <== wait for: Initialized
await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Ready)));
protocol.send(VSBuffer.fromString(JSON.stringify(await this._createExtHostInitData())));
await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Initialized)));
// Register log channel for web worker exthost log
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id: 'webWorkerExtHostLog', label: localize('name', "Worker Extension Host"), file: this._extensionHostLogFile, log: true });
this._protocol = protocol;
public async start(): Promise<IMessagePassingProtocol> {
if (!this._protocolPromise) {
if (WRAP_IN_IFRAME && platform.isWeb) {
this._protocolPromise = this._startInsideIframe();
} else {
this._protocolPromise = this._startOutsideIframe();
}
this._protocolPromise.then(protocol => this._protocol = protocol);
}
return this._protocol;
return this._protocolPromise;
}
dispose(): void {
if (!this._protocol) {
this._toDispose.dispose();
return;
private _startInsideIframe(): Promise<IMessagePassingProtocol> {
const emitter = this._register(new Emitter<VSBuffer>());
const iframe = document.createElement('iframe');
iframe.setAttribute('class', 'web-worker-ext-host-iframe');
iframe.setAttribute('sandbox', 'allow-scripts');
iframe.style.display = 'none';
const vscodeWebWorkerExtHostId = generateUuid();
const workerUrl = require.toUrl('../worker/extensionHostWorkerMain.js');
const workerSrc = getWorkerBootstrapUrl(workerUrl, 'WorkerExtensionHost', true);
const escapeAttribute = (value: string): string => {
return value.replace(/"/g, '&quot;');
};
const html = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-eval' '${WEB_WORKER_IFRAME.sha}' *; worker-src data:; connect-src *" />
<meta id="vscode-worker-src" data-value="${escapeAttribute(workerSrc)}" />
<meta id="vscode-web-worker-ext-host-id" data-value="${escapeAttribute(vscodeWebWorkerExtHostId)}" />
</head>
<body>
<script>${WEB_WORKER_IFRAME.js}</script>
</body>
</html>`;
const iframeContent = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
iframe.setAttribute('src', iframeContent);
this._register(dom.addDisposableListener(window, 'message', (event) => {
if (event.source !== iframe.contentWindow) {
return;
}
if (event.data.vscodeWebWorkerExtHostId !== vscodeWebWorkerExtHostId) {
return;
}
if (event.data.error) {
const { name, message, stack } = event.data.error;
const err = new Error();
err.message = message;
err.name = name;
err.stack = stack;
onUnexpectedError(err);
this._onDidExit.fire([18, err.message]);
return;
}
const { data } = event.data;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
return;
}
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
}));
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
iframe.contentWindow!.postMessage({
vscodeWebWorkerExtHostId,
data: data
}, '*', [data]);
}
};
document.body.appendChild(iframe);
this._register(toDisposable(() => iframe.remove()));
return this._performHandshake(protocol);
}
private _startOutsideIframe(): Promise<IMessagePassingProtocol> {
const emitter = new Emitter<VSBuffer>();
const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost');
const worker = new Worker(url, { name: 'WorkerExtensionHost' });
worker.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
return;
}
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
};
worker.onerror = (event) => {
console.error(event.message, event.error);
this._onDidExit.fire([81, event.message || event.error]);
};
// keep for cleanup
this._register(emitter);
this._register(toDisposable(() => worker.terminate()));
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
worker.postMessage(data, [data]);
}
};
return this._performHandshake(protocol);
}
private async _performHandshake(protocol: IMessagePassingProtocol): Promise<IMessagePassingProtocol> {
// extension host handshake happens below
// (1) <== wait for: Ready
// (2) ==> send: init data
// (3) <== wait for: Initialized
await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Ready)));
if (this._isTerminating) {
throw canceled();
}
protocol.send(VSBuffer.fromString(JSON.stringify(await this._createExtHostInitData())));
if (this._isTerminating) {
throw canceled();
}
await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Initialized)));
if (this._isTerminating) {
throw canceled();
}
// Register log channel for web worker exthost log
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id: 'webWorkerExtHostLog', label: localize('name', "Worker Extension Host"), file: this._extensionHostLogFile, log: true });
return protocol;
}
public dispose(): void {
if (this._isTerminating) {
return;
}
this._isTerminating = true;
this._protocol.send(createMessageOfType(MessageType.Terminate));
setTimeout(() => this._toDispose.dispose(), 10 * 1000);
if (this._protocol) {
this._protocol.send(createMessageOfType(MessageType.Terminate));
}
super.dispose();
}
getInspectPort(): number | undefined {

View File

@@ -20,7 +20,7 @@ import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensi
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { ExtensionIdentifier, IExtensionDescription, IScannedExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -29,7 +29,7 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
export function parseScannedExtension(extension: IScannedExtension): IExtensionDescription {
export function parseScannedExtension(extension: ITranslatedScannedExtension): IExtensionDescription {
return {
identifier: new ExtensionIdentifier(`${extension.packageJSON.publisher}.${extension.packageJSON.name}`),
isBuiltin: extension.type === ExtensionType.System,

View File

@@ -27,7 +27,6 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
const LOG_USE_COLORS = true;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
export class ExtensionHostManager extends Disposable {
@@ -38,9 +37,9 @@ export class ExtensionHostManager extends Disposable {
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
/**
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
* A map of already requested activation events to speed things up if the same activation event is triggered multiple times.
*/
private readonly _finishedActivateEvents: { [activationEvent: string]: boolean; };
private readonly _cachedActivationEvents: Map<string, Promise<void>>;
private _rpcProtocol: RPCProtocol | null;
private readonly _customers: IDisposable[];
private readonly _extensionHost: IExtensionHost;
@@ -57,7 +56,7 @@ export class ExtensionHostManager extends Disposable {
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
) {
super();
this._finishedActivateEvents = Object.create(null);
this._cachedActivationEvents = new Map<string, Promise<void>>();
this._rpcProtocol = null;
this._customers = [];
@@ -221,19 +220,23 @@ export class ExtensionHostManager extends Disposable {
}
public activateByEvent(activationEvent: string): Promise<void> {
if (this._finishedActivateEvents[activationEvent] || !this._proxy) {
return NO_OP_VOID_PROMISE;
if (!this._cachedActivationEvents.has(activationEvent)) {
this._cachedActivationEvents.set(activationEvent, this._activateByEvent(activationEvent));
}
return this._proxy.then((proxy) => {
if (!proxy) {
// this case is already covered above and logged.
// i.e. the extension host could not be started
return NO_OP_VOID_PROMISE;
}
return proxy.value.$activateByEvent(activationEvent);
}).then(() => {
this._finishedActivateEvents[activationEvent] = true;
});
return this._cachedActivationEvents.get(activationEvent)!;
}
private async _activateByEvent(activationEvent: string): Promise<void> {
if (!this._proxy) {
return;
}
const proxy = await this._proxy;
if (!proxy) {
// this case is already covered above and logged.
// i.e. the extension host could not be started
return;
}
return proxy.value.$activateByEvent(activationEvent);
}
public async getInspectPort(tryEnableInspector: boolean): Promise<number> {

View File

@@ -150,11 +150,13 @@ const extensionKindSchema: IJSONSchema = {
type: 'string',
enum: [
'ui',
'workspace'
'workspace',
'web'
],
enumDescriptions: [
nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."),
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.")
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote."),
nls.localize('web', "Web worker extension kind. Such an extension can execute in a web worker extension host.")
],
};

View File

@@ -59,18 +59,29 @@ export function getExtensionKind(manifest: IExtensionManifest, productService: I
return toArray(result);
}
return deduceExtensionKind(manifest);
}
export function deduceExtensionKind(manifest: IExtensionManifest): ExtensionKind[] {
// Not an UI extension if it has main
if (manifest.main) {
if (manifest.browser) {
return ['workspace', 'web'];
}
return ['workspace'];
}
// Not an UI extension if it has dependencies or an extension pack
if (manifest.browser) {
return ['web'];
}
// Not an UI nor web extension if it has dependencies or an extension pack
if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) {
return ['workspace'];
}
if (manifest.contributes) {
// Not an UI extension if it has no ui contributions
// Not an UI nor web extension if it has no ui contributions
for (const contribution of Object.keys(manifest.contributes)) {
if (!isUIExtensionPoint(contribution)) {
return ['workspace'];
@@ -78,7 +89,7 @@ export function getExtensionKind(manifest: IExtensionManifest, productService: I
}
}
return ['ui', 'workspace'];
return ['ui', 'workspace', 'web'];
}
let _uiExtensionPoints: Set<string> | null = null;

View File

@@ -96,7 +96,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
}
},
signService: this._signService,
logService: this._logService
logService: this._logService,
ipcLogger: null
};
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => {
@@ -204,8 +205,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
const [telemetryInfo, remoteInitData] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]);
// Collect all identifiers for extension ids which can be considered "resolved"
const resolvedExtensions = remoteInitData.allExtensions.filter(extension => !extension.main).map(extension => extension.identifier);
const hostExtensions = remoteInitData.allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
const resolvedExtensions = remoteInitData.allExtensions.filter(extension => !extension.main && !extension.browser).map(extension => extension.identifier);
const hostExtensions = remoteInitData.allExtensions.filter(extension => (extension.main || extension.browser) && extension.api === 'none').map(extension => extension.identifier);
const workspace = this._contextService.getWorkspace();
return {
commit: this._productService.commit,

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const WEB_WORKER_IFRAME = {
sha: 'sha256-rSINb5Ths99Zj4Ml59jEdHS4WbO+H5Iw+oyRmyi2MLw=',
js: `
(function() {
const workerSrc = document.getElementById('vscode-worker-src').getAttribute('data-value');
const worker = new Worker(workerSrc, { name: 'WorkerExtensionHost' });
const vscodeWebWorkerExtHostId = document.getElementById('vscode-web-worker-ext-host-id').getAttribute('data-value');
worker.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('Unknown data received', data);
window.parent.postMessage({
vscodeWebWorkerExtHostId,
error: {
name: 'Error',
message: 'Unknown data received',
stack: []
}
}, '*');
return;
}
window.parent.postMessage({
vscodeWebWorkerExtHostId,
data: data
}, '*', [data]);
};
worker.onerror = (event) => {
console.error(event.message, event.error);
window.parent.postMessage({
vscodeWebWorkerExtHostId,
error: {
name: event.error ? event.error.name : '',
message: event.error ? event.error.message : '',
stack: event.error ? event.error.stack : []
}
}, '*');
};
window.addEventListener('message', function(event) {
if (event.source !== window.parent) {
return;
}
if (event.data.vscodeWebWorkerExtHostId !== vscodeWebWorkerExtHostId) {
return;
}
worker.postMessage(event.data.data, [event.data.data]);
}, false);
})();
`
};

View File

@@ -24,7 +24,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExtensionService, toExtension, ExtensionHostKind, IExtensionHost, webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
@@ -377,7 +377,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
private async _scanAllLocalExtensions(): Promise<IExtensionDescription[]> {
return flatten(await Promise.all([
this._extensionScanner.scannedExtensions,
this._webExtensionsScannerService.scanExtensions().then(extensions => extensions.map(parseScannedExtension))
this._webExtensionsScannerService.scanAndTranslateExtensions().then(extensions => extensions.map(parseScannedExtension))
]));
}
@@ -386,7 +386,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
getInitData: async () => {
if (isInitialStart) {
const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions());
const runningLocation = determineRunningLocation(this._productService, this._configurationService, localExtensions, [], false, this._enableLocalWebWorker);
const runningLocation = _determineRunningLocation(this._productService, this._configurationService, localExtensions, [], false, this._enableLocalWebWorker);
const localProcessExtensions = filterByRunningLocation(localExtensions, runningLocation, desiredRunningLocation);
return {
autoStart: false,
@@ -503,8 +503,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!;
let localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions());
const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions());
let remoteEnv: IRemoteAgentEnvironment | null = null;
let remoteExtensions: IExtensionDescription[] = [];
if (remoteAuthority) {
let resolverResult: ResolverResult;
@@ -543,7 +544,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
// fetch the remote environment
remoteEnv = await this._remoteAgentService.getEnvironment();
[remoteEnv, remoteExtensions] = await Promise.all([
this._remoteAgentService.getEnvironment(),
this._remoteAgentService.scanExtensions()
]);
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions);
if (!remoteEnv) {
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('getEnvironmentFailure', "Could not fetch remote environment") });
@@ -553,14 +558,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
}
await this._startLocalExtensionHost(localExtensions, remoteAuthority, remoteEnv);
await this._startLocalExtensionHost(localExtensions, remoteAuthority, remoteEnv, remoteExtensions);
}
private async _startLocalExtensionHost(localExtensions: IExtensionDescription[], remoteAuthority: string | undefined = undefined, remoteEnv: IRemoteAgentEnvironment | null = null): Promise<void> {
private async _startLocalExtensionHost(localExtensions: IExtensionDescription[], remoteAuthority: string | undefined = undefined, remoteEnv: IRemoteAgentEnvironment | null = null, remoteExtensions: IExtensionDescription[] = []): Promise<void> {
let remoteExtensions = remoteEnv ? this._checkEnabledAndProposedAPI(remoteEnv.extensions) : [];
this._runningLocation = determineRunningLocation(this._productService, this._configurationService, localExtensions, remoteExtensions, Boolean(remoteAuthority), this._enableLocalWebWorker);
this._runningLocation = _determineRunningLocation(this._productService, this._configurationService, localExtensions, remoteExtensions, Boolean(remoteAuthority), this._enableLocalWebWorker);
// remove non-UI extensions from the local extensions
const localProcessExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalProcess);
@@ -686,7 +689,7 @@ const enum ExtensionRunningLocation {
Remote
}
function determineRunningLocation(productService: IProductService, configurationService: IConfigurationService, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], hasRemote: boolean, hasLocalWebWorker: boolean): Map<string, ExtensionRunningLocation> {
export function determineRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], allExtensionKinds: Map<string, ExtensionKind[]>, hasRemote: boolean, hasLocalWebWorker: boolean): Map<string, ExtensionRunningLocation> {
const localExtensionsSet = new Set<string>();
localExtensions.forEach(ext => localExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
@@ -696,7 +699,8 @@ function determineRunningLocation(productService: IProductService, configuration
const pickRunningLocation = (extension: IExtensionDescription): ExtensionRunningLocation => {
const isInstalledLocally = localExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
const isInstalledRemotely = remoteExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
for (const extensionKind of getExtensionKind(extension, productService, configurationService)) {
const extensionKinds = allExtensionKinds.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
for (const extensionKind of extensionKinds) {
if (extensionKind === 'ui' && isInstalledLocally) {
// ui extensions run locally if possible
return ExtensionRunningLocation.LocalProcess;
@@ -711,10 +715,6 @@ function determineRunningLocation(productService: IProductService, configuration
}
if (extensionKind === 'web' && isInstalledLocally && hasLocalWebWorker) {
// web worker extensions run in the local web worker if possible
if (typeof extension.browser !== 'undefined') {
// The "browser" field determines the entry point
(<any>extension).main = extension.browser;
}
return ExtensionRunningLocation.LocalWebWorker;
}
}
@@ -727,6 +727,13 @@ function determineRunningLocation(productService: IProductService, configuration
return runningLocation;
}
function _determineRunningLocation(productService: IProductService, configurationService: IConfigurationService, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], hasRemote: boolean, hasLocalWebWorker: boolean): Map<string, ExtensionRunningLocation> {
const allExtensionKinds = new Map<string, ExtensionKind[]>();
localExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), getExtensionKind(ext, productService, configurationService)));
remoteExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), getExtensionKind(ext, productService, configurationService)));
return determineRunningLocation(localExtensions, remoteExtensions, allExtensionKinds, hasRemote, hasLocalWebWorker);
}
function filterByRunningLocation(extensions: IExtensionDescription[], runningLocation: Map<string, ExtensionRunningLocation>, desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] {
return extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === desiredRunningLocation);
}

View File

@@ -44,7 +44,6 @@ import { joinPath } from 'vs/base/common/resources';
import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { isUUID } from 'vs/base/common/uuid';
export interface ILocalProcessExtensionHostInitData {
readonly autoStart: boolean;
@@ -183,23 +182,18 @@ export class LocalProcessExtensionHost implements IExtensionHost {
opts.execArgv = ['--inspect-port=0'];
}
// On linux crash reporter needs to be started on child node processes explicitly
if (platform.isLinux) {
const crashReporterStartOptions: CrashReporterStartOptions = {
// Enable the crash reporter depending on environment for local reporting
const crashesDirectory = this._environmentService.crashReporterDirectory;
if (crashesDirectory) {
const crashReporterOptions: CrashReporterStartOptions = {
companyName: this._productService.crashReporter?.companyName || 'Microsoft',
productName: this._productService.crashReporter?.productName || this._productService.nameShort,
submitURL: '',
uploadToServer: false
uploadToServer: false,
crashesDirectory
};
const crashReporterId = this._environmentService.crashReporterId; // crashReporterId is set by the main process only when crash reporting is enabled by the user.
const appcenter = this._productService.appCenter;
const uploadCrashesToServer = !this._environmentService.crashReporterDirectory; // only upload unless --crash-reporter-directory is provided
if (uploadCrashesToServer && appcenter && crashReporterId && isUUID(crashReporterId)) {
const submitURL = appcenter[`linux-x64`];
crashReporterStartOptions.submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId);
crashReporterStartOptions.uploadToServer = true;
}
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions);
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions);
}
// Run Extension Host as fork of current process

View File

@@ -393,9 +393,8 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
notices.push(nls.localize('extensionDescription.main1', "property `{0}` can be omitted or must be of type `string`", 'main'));
return false;
} else {
let normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.main);
if (normalizedAbsolutePath.indexOf(extensionFolderPath)) {
const normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.main);
if (!normalizedAbsolutePath.startsWith(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
}
@@ -405,6 +404,22 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
return false;
}
}
if (typeof extensionDescription.browser !== 'undefined') {
if (typeof extensionDescription.browser !== 'string') {
notices.push(nls.localize('extensionDescription.browser1', "property `{0}` can be omitted or must be of type `string`", 'browser'));
return false;
} else {
const normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.browser);
if (!normalizedAbsolutePath.startsWith(extensionFolderPath)) {
notices.push(nls.localize('extensionDescription.browser2', "Expected `browser` ({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.browser3', "properties `{0}` and `{1}` must both be specified or must both be omitted", 'activationEvents', 'browser'));
return false;
}
}
return true;
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { deduceExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IExtensionManifest, ExtensionKind } from 'vs/platform/extensions/common/extensions';
suite('ExtensionKind', () => {
function check(manifest: Partial<IExtensionManifest>, expected: ExtensionKind[]): void {
assert.deepEqual(deduceExtensionKind(<IExtensionManifest>manifest), expected);
}
test('declarative with extension dependencies => workspace', () => {
check({ extensionDependencies: ['ext1'] }, ['workspace']);
});
test('declarative extension pack => workspace', () => {
check({ extensionPack: ['ext1', 'ext2'] }, ['workspace']);
});
test('declarative with unknown contribution point => workspace', () => {
check({ contributes: <any>{ 'unknownPoint': { something: true } } }, ['workspace']);
});
test('simple declarative => ui, workspace, web', () => {
check({}, ['ui', 'workspace', 'web']);
});
test('only browser => web', () => {
check({ browser: 'main.browser.js' }, ['web']);
});
test('only main => workspace', () => {
check({ main: 'main.js' }, ['workspace']);
});
test('main and browser => workspace, web', () => {
check({ main: 'main.js', browser: 'main.browser.js' }, ['workspace', 'web']);
});
});

View File

@@ -10,6 +10,7 @@ import { isMessageOfType, MessageType, createMessageOfType } from 'vs/workbench/
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { ExtensionHostMain } from 'vs/workbench/services/extensions/common/extensionHostMain';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import * as path from 'vs/base/common/path';
import 'vs/workbench/api/common/extHost.common.services';
import 'vs/workbench/api/worker/extHost.worker.services';
@@ -35,6 +36,17 @@ self.postMessage = () => console.trace(`'postMessage' has been blocked`);
const nativeAddEventLister = addEventListener.bind(self);
self.addEventLister = () => console.trace(`'addEventListener' has been blocked`);
if (location.protocol === 'data:') {
// make sure new Worker(...) always uses data:
const _Worker = Worker;
Worker = <any>function (stringUrl: string | URL, options?: WorkerOptions) {
const js = `importScripts('${stringUrl}');`;
options = options || {};
options.name = options.name || path.basename(stringUrl.toString());
return new _Worker(`data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`, options);
};
}
//#endregion ---
const hostUtil = new class implements IHostUtils {

View File

@@ -51,6 +51,10 @@ const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoin
type: 'string',
description: localize('vscode.extension.contributes.resourceLabelFormatters.separator', "Separator to be used in the uri label display. '/' or '\' as an example.")
},
stripPathStartingSeparator: {
type: 'boolean',
description: localize('vscode.extension.contributes.resourceLabelFormatters.stripPathStartingSeparator', "Controls whether `${path}` substitutions should have starting separator characters stripped.")
},
tildify: {
type: 'boolean',
description: localize('vscode.extension.contributes.resourceLabelFormatters.tildify', "Controls if the start of the uri label should be tildified when possible.")
@@ -244,7 +248,10 @@ export class LabelService extends Disposable implements ILabelService {
switch (token) {
case 'scheme': return resource.scheme;
case 'authority': return resource.authority;
case 'path': return resource.path;
case 'path':
return formatting.stripPathStartingSeparator
? resource.path.slice(resource.path[0] === formatting.separator ? 1 : 0)
: resource.path;
default: {
if (qsToken === 'query') {
const { query } = resource;

View File

@@ -226,6 +226,26 @@ suite('multi-root worksapce', () => {
const generated = labelService.getUriLabel(URI.file(path), { relative: true });
assert.equal(generated, label, path);
});
});
test('stripPathStartingSeparator', () => {
labelService.registerFormatter({
scheme: 'file',
formatting: {
label: '${path}',
separator: '/',
stripPathStartingSeparator: true
}
});
const tests = {
'folder1/src/file': 'Sources • file',
'other/blah': 'other/blah',
};
Object.entries(tests).forEach(([path, label]) => {
const generated = labelService.getUriLabel(URI.file(path), { relative: true });
assert.equal(generated, label, path);
});
});
});

View File

@@ -4,21 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { IProductService } from 'vs/platform/product/common/productService';
import { IWebSocketFactory, BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
import { ISignService } from 'vs/platform/sign/common/sign';
import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { ILogService } from 'vs/platform/log/common/log';
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
public readonly socketFactory: ISocketFactory;
private readonly _connection: IRemoteAgentConnection | null = null;
constructor(
webSocketFactory: IWebSocketFactory | null | undefined,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@@ -27,16 +22,6 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR
@ISignService signService: ISignService,
@ILogService logService: ILogService
) {
super(environmentService, remoteAuthorityResolverService);
this.socketFactory = new BrowserSocketFactory(webSocketFactory);
const remoteAuthority = environmentService.configuration.remoteAuthority;
if (remoteAuthority) {
this._connection = this._register(new RemoteAgentConnection(remoteAuthority, productService.commit, this.socketFactory, remoteAuthorityResolverService, signService, logService));
}
}
getConnection(): IRemoteAgentConnection | null {
return this._connection;
super(new BrowserSocketFactory(webSocketFactory), environmentService, productService, remoteAuthorityResolverService, signService, logService);
}
}

View File

@@ -5,9 +5,9 @@
import * as nls from 'vs/nls';
import { Disposable } from 'vs/base/common/lifecycle';
import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { IChannel, IServerChannel, getDelayedChannel, IPCLogger } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { connectRemoteAgentManagement, IConnectionOptions, ISocketFactory, PersistenConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
@@ -22,35 +22,62 @@ import { Emitter } from 'vs/base/common/event';
import { ISignService } from 'vs/platform/sign/common/sign';
import { ILogService } from 'vs/platform/log/common/log';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IProductService } from 'vs/platform/product/common/productService';
export abstract class AbstractRemoteAgentService extends Disposable {
export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService {
declare readonly _serviceBrand: undefined;
public readonly socketFactory: ISocketFactory;
private readonly _connection: IRemoteAgentConnection | null;
private _environment: Promise<IRemoteAgentEnvironment | null> | null;
constructor(
@IEnvironmentService protected readonly _environmentService: IEnvironmentService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService
socketFactory: ISocketFactory,
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
@IProductService productService: IProductService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@ISignService signService: ISignService,
@ILogService logService: ILogService
) {
super();
this.socketFactory = socketFactory;
if (this._environmentService.configuration.remoteAuthority) {
this._connection = this._register(new RemoteAgentConnection(this._environmentService.configuration.remoteAuthority, productService.commit, this.socketFactory, this._remoteAuthorityResolverService, signService, logService));
} else {
this._connection = null;
}
this._environment = null;
}
abstract getConnection(): IRemoteAgentConnection | null;
getConnection(): IRemoteAgentConnection | null {
return this._connection;
}
getEnvironment(bail?: boolean): Promise<IRemoteAgentEnvironment | null> {
getEnvironment(): Promise<IRemoteAgentEnvironment | null> {
return this.getRawEnvironment().then(undefined, () => null);
}
getRawEnvironment(): Promise<IRemoteAgentEnvironment | null> {
if (!this._environment) {
this._environment = this._withChannel(
async (channel, connection) => {
const env = await RemoteExtensionEnvironmentChannelClient.getEnvironmentData(channel, connection.remoteAuthority, this._environmentService.extensionDevelopmentLocationURI);
const env = await RemoteExtensionEnvironmentChannelClient.getEnvironmentData(channel, connection.remoteAuthority);
this._remoteAuthorityResolverService._setAuthorityConnectionToken(connection.remoteAuthority, env.connectionToken);
return env;
},
null
);
}
return bail ? this._environment : this._environment.then(undefined, () => null);
return this._environment;
}
scanExtensions(skipExtensions: ExtensionIdentifier[] = []): Promise<IExtensionDescription[]> {
return this._withChannel(
(channel, connection) => RemoteExtensionEnvironmentChannelClient.scanExtensions(channel, connection.remoteAuthority, this._environmentService.extensionDevelopmentLocationURI, skipExtensions),
[]
).then(undefined, () => []);
}
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined> {
@@ -152,7 +179,8 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon
}
},
signService: this._signService,
logService: this._logService
logService: this._logService,
ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null
};
const connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`));
this._register(connection.onDidStateChange(e => this._onDidStateChange.fire(e)));
@@ -167,7 +195,7 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr
@INotificationService notificationService: INotificationService,
) {
// Let's cover the case where connecting to fetch the remote extension info fails
remoteAgentService.getEnvironment(true)
remoteAgentService.getRawEnvironment()
.then(undefined, err => {
if (!RemoteAuthorityResolverError.isHandled(err)) {
notificationService.error(nls.localize('connectionError', "Failed to connect to the remote extension host server (Error: {0})", err ? err.message : ''));

View File

@@ -6,15 +6,20 @@
import * as platform from 'vs/base/common/platform';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
export interface IGetEnvironmentDataArguments {
remoteAuthority: string;
}
export interface IScanExtensionsArguments {
language: string;
remoteAuthority: string;
extensionDevelopmentPath: UriComponents[] | undefined;
skipExtensions: ExtensionIdentifier[];
}
export interface IRemoteAgentEnvironmentDTO {
@@ -28,17 +33,14 @@ export interface IRemoteAgentEnvironmentDTO {
globalStorageHome: UriComponents;
workspaceStorageHome: UriComponents;
userHome: UriComponents;
extensions: IExtensionDescription[];
os: platform.OperatingSystem;
}
export class RemoteExtensionEnvironmentChannelClient {
static async getEnvironmentData(channel: IChannel, remoteAuthority: string, extensionDevelopmentPath?: URI[]): Promise<IRemoteAgentEnvironment> {
static async getEnvironmentData(channel: IChannel, remoteAuthority: string): Promise<IRemoteAgentEnvironment> {
const args: IGetEnvironmentDataArguments = {
language: platform.language,
remoteAuthority,
extensionDevelopmentPath
remoteAuthority
};
const data = await channel.call<IRemoteAgentEnvironmentDTO>('getEnvironmentData', args);
@@ -54,11 +56,24 @@ export class RemoteExtensionEnvironmentChannelClient {
globalStorageHome: URI.revive(data.globalStorageHome),
workspaceStorageHome: URI.revive(data.workspaceStorageHome),
userHome: URI.revive(data.userHome),
extensions: data.extensions.map(ext => { (<any>ext).extensionLocation = URI.revive(ext.extensionLocation); return ext; }),
os: data.os
};
}
static async scanExtensions(channel: IChannel, remoteAuthority: string, extensionDevelopmentPath: URI[] | undefined, skipExtensions: ExtensionIdentifier[]): Promise<IExtensionDescription[]> {
const args: IScanExtensionsArguments = {
language: platform.language,
remoteAuthority,
extensionDevelopmentPath,
skipExtensions
};
const extensions = await channel.call<IExtensionDescription[]>('scanExtensions', args);
extensions.forEach(ext => { (<any>ext).extensionLocation = URI.revive(ext.extensionLocation); });
return extensions;
}
static getDiagnosticInfo(channel: IChannel, options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo> {
return channel.call<IDiagnosticInfo>('getDiagnosticInfo', options);
}

View File

@@ -10,6 +10,7 @@ import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics
import { Event } from 'vs/base/common/event';
import { PersistenConnectionEvent as PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
export const RemoteExtensionLogFileName = 'remoteagent';
@@ -21,7 +22,18 @@ export interface IRemoteAgentService {
readonly socketFactory: ISocketFactory;
getConnection(): IRemoteAgentConnection | null;
getEnvironment(bail?: boolean): Promise<IRemoteAgentEnvironment | null>;
/**
* Get the remote environment. In case of an error, returns `null`.
*/
getEnvironment(): Promise<IRemoteAgentEnvironment | null>;
/**
* Get the remote environment. Can return an error.
*/
getRawEnvironment(): Promise<IRemoteAgentEnvironment | null>;
/**
* Scan remote extensions.
*/
scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise<IExtensionDescription[]>;
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined>;
disableTelemetry(): Promise<void>;
logTelemetry(eventName: string, data?: ITelemetryData): Promise<void>;

View File

@@ -3,37 +3,23 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IProductService } from 'vs/platform/product/common/productService';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
import { ISignService } from 'vs/platform/sign/common/sign';
import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
public readonly socketFactory: ISocketFactory;
private readonly _connection: IRemoteAgentConnection | null = null;
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IProductService productService: IProductService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@ISignService signService: ISignService,
@ILogService logService: ILogService,
@IProductService productService: IProductService
) {
super(environmentService, remoteAuthorityResolverService);
this.socketFactory = nodeSocketFactory;
if (environmentService.configuration.remoteAuthority) {
this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority, productService.commit, nodeSocketFactory, remoteAuthorityResolverService, signService, logService));
}
}
getConnection(): IRemoteAgentConnection | null {
return this._connection;
super(nodeSocketFactory, environmentService, productService, remoteAuthorityResolverService, signService, logService);
}
}

View File

@@ -57,13 +57,13 @@ export class ReplacePattern {
*/
getReplaceString(text: string, preserveCase?: boolean): string | null {
this._regExp.lastIndex = 0;
let match = this._regExp.exec(text);
const match = this._regExp.exec(text);
if (match) {
if (this.hasParameters) {
if (match[0] === text) {
return text.replace(this._regExp, this.buildReplaceString(match, preserveCase));
}
let replaceString = text.replace(this._regExp, this.buildReplaceString(match, preserveCase));
const replaceString = text.replace(this._regExp, this.buildReplaceString(match, preserveCase));
return replaceString.substr(match.index, match[0].length - (text.length - replaceString.length));
}
return this.buildReplaceString(match, preserveCase);
@@ -94,7 +94,7 @@ export class ReplacePattern {
let substrFrom = 0, result = '';
for (let i = 0, len = replaceString.length; i < len; i++) {
let chCode = replaceString.charCodeAt(i);
const chCode = replaceString.charCodeAt(i);
if (chCode === CharCode.Backslash) {
@@ -106,7 +106,7 @@ export class ReplacePattern {
break;
}
let nextChCode = replaceString.charCodeAt(i);
const nextChCode = replaceString.charCodeAt(i);
let replaceWithCharacter: string | null = null;
switch (nextChCode) {
@@ -140,7 +140,7 @@ export class ReplacePattern {
break;
}
let nextChCode = replaceString.charCodeAt(i);
const nextChCode = replaceString.charCodeAt(i);
let replaceWithCharacter: string | null = null;
switch (nextChCode) {

View File

@@ -179,7 +179,7 @@ export class SearchService extends Disposable implements ISearchService {
}
private async waitForProvider(queryType: QueryType, scheme: string): Promise<ISearchResultProvider> {
let deferredMap: Map<string, DeferredPromise<ISearchResultProvider>> = queryType === QueryType.File ?
const deferredMap: Map<string, DeferredPromise<ISearchResultProvider>> = queryType === QueryType.File ?
this.deferredFileSearchesByScheme :
this.deferredTextSearchesByScheme;

View File

@@ -162,12 +162,12 @@ export function rgErrorMsgForDisplay(msg: string): Maybe<SearchError> {
}
export function buildRegexParseError(lines: string[]): string {
let errorMessage: string[] = ['Regex parse error'];
let pcre2ErrorLine = lines.filter(l => (l.startsWith('PCRE2:')));
const errorMessage: string[] = ['Regex parse error'];
const pcre2ErrorLine = lines.filter(l => (l.startsWith('PCRE2:')));
if (pcre2ErrorLine.length >= 1) {
let pcre2ErrorMessage = pcre2ErrorLine[0].replace('PCRE2:', '');
const pcre2ErrorMessage = pcre2ErrorLine[0].replace('PCRE2:', '');
if (pcre2ErrorMessage.indexOf(':') !== -1 && pcre2ErrorMessage.split(':').length >= 2) {
let pcre2ActualErrorMessage = pcre2ErrorMessage.split(':')[1];
const pcre2ActualErrorMessage = pcre2ErrorMessage.split(':')[1];
errorMessage.push(':' + pcre2ActualErrorMessage);
}
}
@@ -300,12 +300,12 @@ export class RipgrepParser extends EventEmitter {
match.end = match.end <= 3 ? 0 : match.end - 3;
}
const inBetweenChars = fullTextBytes.slice(prevMatchEnd, match.start).toString().length;
let startCol = prevMatchEndCol + inBetweenChars;
const startCol = prevMatchEndCol + inBetweenChars;
const stats = getNumLinesAndLastNewlineLength(matchText);
const startLineNumber = prevMatchEndLine;
const endLineNumber = stats.numLines + startLineNumber;
let endCol = stats.numLines > 0 ?
const endCol = stats.numLines > 0 ?
stats.lastLineLength :
stats.lastLineLength + startCol;

View File

@@ -8,7 +8,7 @@ import { ReplacePattern } from 'vs/workbench/services/search/common/replace';
suite('Replace Pattern test', () => {
test('parse replace string', () => {
let testParse = (input: string, expected: string, expectedHasParameters: boolean) => {
const testParse = (input: string, expected: string, expectedHasParameters: boolean) => {
let actual = new ReplacePattern(input, { pattern: 'somepattern', isRegExp: true });
assert.equal(expected, actual.pattern);
assert.equal(expectedHasParameters, actual.hasParameters);

View File

@@ -387,7 +387,7 @@ suite('TextSearch-integration', function () {
throw new Error('expected fail');
}, err => {
const searchError = deserializeSearchError(err);
let regexParseErrorForUnclosedParenthesis = 'Regex parse error: unmatched closing parenthesis';
const regexParseErrorForUnclosedParenthesis = 'Regex parse error: unmatched closing parenthesis';
assert.equal(searchError.message, regexParseErrorForUnclosedParenthesis);
assert.equal(searchError.code, SearchErrorCode.regexParseError);
});
@@ -404,7 +404,7 @@ suite('TextSearch-integration', function () {
throw new Error('expected fail');
}, err => {
const searchError = deserializeSearchError(err);
let regexParseErrorForLookAround = 'Regex parse error: lookbehind assertion is not fixed length';
const regexParseErrorForLookAround = 'Regex parse error: lookbehind assertion is not fixed length';
assert.equal(searchError.message, regexParseErrorForLookAround);
assert.equal(searchError.code, SearchErrorCode.regexParseError);
});

View File

@@ -3,21 +3,20 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask, IUserDataSyncStoreManagementService, UserDataSyncStoreType, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, CONTEXT_ENABLE_SYNC_MERGES_VIEW, SYNC_MERGES_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, CONTEXT_ENABLE_SYNC_MERGES_VIEW, SYNC_MERGES_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE } from 'vs/workbench/services/userDataSync/common/userDataSync';
import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { flatten, equals } from 'vs/base/common/arrays';
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
import { getAuthenticationProviderActivationEvent, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { localize } from 'vs/nls';
@@ -30,6 +29,9 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IViewsService, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
import { isNative } from 'vs/base/common/platform';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
type UserAccountClassification = {
id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' };
@@ -64,7 +66,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
private static DONOT_USE_WORKBENCH_SESSION_STORAGE_KEY = 'userDataSyncAccount.donotUseWorkbenchSession';
private static CACHED_SESSION_STORAGE_KEY = 'userDataSyncAccountPreference';
readonly authenticationProviders: IAuthenticationProvider[];
private _authenticationProviders: IAuthenticationProvider[] = [];
get enabled() { return this._authenticationProviders.length > 0; }
private availableAuthenticationProviders: IAuthenticationProvider[] = [];
get authenticationProviders() { return this.availableAuthenticationProviders; }
private _accountStatus: AccountStatus = AccountStatus.Uninitialized;
get accountStatus(): AccountStatus { return this._accountStatus; }
@@ -93,9 +99,8 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ILogService private readonly logService: ILogService,
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@IExtensionService extensionService: IExtensionService,
@IProductService private readonly productService: IProductService,
@IExtensionService private readonly extensionService: IExtensionService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@INotificationService private readonly notificationService: INotificationService,
@IProgressService private readonly progressService: IProgressService,
@@ -103,37 +108,50 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
@IContextKeyService contextKeyService: IContextKeyService,
@IViewsService private readonly viewsService: IViewsService,
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
@IHostService private readonly hostService: IHostService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
) {
super();
this.authenticationProviders = getUserDataSyncStore(productService, configurationService)?.authenticationProviders || [];
this._authenticationProviders = this.userDataSyncStoreManagementService.userDataSyncStore?.authenticationProviders || [];
this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService);
this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
this.accountStatusContext = CONTEXT_ACCOUNT_STATE.bindTo(contextKeyService);
this.activityViewsEnablementContext = CONTEXT_ENABLE_ACTIVITY_VIEWS.bindTo(contextKeyService);
this.mergesViewEnablementContext = CONTEXT_ENABLE_SYNC_MERGES_VIEW.bindTo(contextKeyService);
if (this.authenticationProviders.length) {
if (this._authenticationProviders.length) {
this.syncStatusContext.set(this.userDataSyncService.status);
this._register(userDataSyncService.onDidChangeStatus(status => this.syncStatusContext.set(status)));
this.syncEnablementContext.set(userDataAutoSyncService.isEnabled());
this._register(userDataAutoSyncService.onDidChangeEnablement(enabled => this.syncEnablementContext.set(enabled)));
extensionService.whenInstalledExtensionsRegistered().then(() => {
if (this.authenticationProviders.every(({ id }) => authenticationService.isAuthenticationProviderRegistered(id))) {
this.initialize();
} else {
const disposable = this.authenticationService.onDidRegisterAuthenticationProvider(() => {
if (this.authenticationProviders.every(({ id }) => authenticationService.isAuthenticationProviderRegistered(id))) {
disposable.dispose();
this.initialize();
}
});
}
});
this.waitAndInitialize();
}
}
private isSupportedAuthenticationProviderId(authenticationProviderId: string): boolean {
return this._authenticationProviders.some(({ id }) => id === authenticationProviderId);
}
private async waitAndInitialize(): Promise<void> {
await this.extensionService.whenInstalledExtensionsRegistered();
/* activate unregistered providers */
const unregisteredProviders = this._authenticationProviders.filter(({ id }) => !this.authenticationService.isAuthenticationProviderRegistered(id));
if (unregisteredProviders.length) {
await Promise.all(unregisteredProviders.map(({ id }) => this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id))));
}
/* wait until all providers are availabe */
if (this._authenticationProviders.some(({ id }) => !this.authenticationService.isAuthenticationProviderRegistered(id))) {
await Event.toPromise(Event.filter(this.authenticationService.onDidRegisterAuthenticationProvider, () => this._authenticationProviders.every(({ id }) => this.authenticationService.isAuthenticationProviderRegistered(id))));
}
/* initialize */
await this.initialize();
}
private async initialize(): Promise<void> {
if (this.currentSessionId === undefined && this.useWorkbenchSessionId && this.environmentService.options?.authenticationSessionId) {
this.currentSessionId = this.environmentService.options.authenticationSessionId;
@@ -158,8 +176,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
}
private async update(): Promise<void> {
this.availableAuthenticationProviders = this._authenticationProviders.filter(({ id }) => this.authenticationService.isAuthenticationProviderRegistered(id));
const allAccounts: Map<string, UserDataSyncAccount[]> = new Map<string, UserDataSyncAccount[]>();
for (const { id } of this.authenticationProviders) {
for (const { id } of this.availableAuthenticationProviders) {
const accounts = await this.getAccounts(id);
allAccounts.set(id, accounts);
}
@@ -167,7 +188,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
this._all = allAccounts;
const current = this.current;
await this.updateToken(current);
this.updateAccountStatus(current);
this.updateAccountStatus(current ? AccountStatus.Available : AccountStatus.Unavailable);
}
private async getAccounts(authenticationProviderId: string): Promise<UserDataSyncAccount[]> {
@@ -195,9 +216,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
let value: { token: string, authenticationProviderId: string } | undefined = undefined;
if (current) {
try {
this.logService.trace('Preferences Sync: Updating the token for the account', current.accountName);
this.logService.trace('Settings Sync: Updating the token for the account', current.accountName);
const token = current.token;
this.logService.trace('Preferences Sync: Token updated for the account', current.accountName);
this.logService.trace('Settings Sync: Token updated for the account', current.accountName);
value = { token, authenticationProviderId: current.authenticationProviderId };
} catch (e) {
this.logService.error(e);
@@ -206,10 +227,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
await this.userDataSyncAccountService.updateAccount(value);
}
private updateAccountStatus(current: UserDataSyncAccount | undefined): void {
// set status
const accountStatus: AccountStatus = current ? AccountStatus.Available : AccountStatus.Unavailable;
private updateAccountStatus(accountStatus: AccountStatus): void {
if (this._accountStatus !== accountStatus) {
const previous = this._accountStatus;
this.logService.debug('Sync account status changed', previous, accountStatus);
@@ -221,6 +239,13 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
}
async turnOn(): Promise<void> {
if (this.userDataAutoSyncService.isEnabled()) {
return;
}
if (this.userDataSyncService.status !== SyncStatus.Idle) {
throw new Error('Cannont turn on sync while syncing');
}
const picked = await this.pick();
if (!picked) {
throw canceled();
@@ -231,9 +256,17 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
throw new Error(localize('no account', "No account available"));
}
const preferencesSyncTitle = localize('preferences sync', "Preferences Sync");
const title = `${preferencesSyncTitle} [(${localize('details', "details")})](command:${SHOW_SYNC_LOG_COMMAND_ID})`;
await this.syncBeforeTurningOn(title);
const syncTitle = SYNC_TITLE;
const title = `${syncTitle} [(${localize('details', "details")})](command:${SHOW_SYNC_LOG_COMMAND_ID})`;
const manualSyncTask = await this.userDataSyncService.createManualSyncTask();
const disposable = this.lifecycleService.onBeforeShutdown(e => e.veto(this.onBeforeShutdown(manualSyncTask)));
try {
await this.syncBeforeTurningOn(title, manualSyncTask);
} finally {
disposable.dispose();
}
await this.userDataAutoSyncService.turnOn();
this.notificationService.info(localize('sync turned on', "{0} is turned on", title));
}
@@ -242,15 +275,27 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
return this.userDataAutoSyncService.turnOff(everywhere);
}
private async syncBeforeTurningOn(title: string): Promise<void> {
private async onBeforeShutdown(manualSyncTask: IManualSyncTask): Promise<boolean> {
const result = await this.dialogService.confirm({
type: 'warning',
message: localize('sync in progress', "Settings Sync is being turned on. Would you like to cancel it?"),
title: localize('settings sync', "Settings Sync"),
primaryButton: localize('yes', "Yes"),
secondaryButton: localize('no', "No"),
});
if (result.confirmed) {
await manualSyncTask.stop();
}
return !result.confirmed;
}
private async syncBeforeTurningOn(title: string, manualSyncTask: IManualSyncTask): Promise<void> {
/* Make sure sync started on clean local state */
await this.userDataSyncService.resetLocal();
const manualSyncTask = await this.userDataSyncService.createManualSyncTask();
try {
let action: FirstTimeSyncAction = 'manual';
let preview: [SyncResource, ISyncResourcePreview][] = [];
await this.progressService.withProgress({
location: ProgressLocation.Notification,
@@ -259,18 +304,24 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
}, async progress => {
progress.report({ message: localize('turning on', "Turning on...") });
preview = await manualSyncTask.preview();
const preview = await manualSyncTask.preview();
const hasRemoteData = manualSyncTask.manifest !== null;
const hasLocalData = await this.userDataSyncService.hasLocalData();
const hasChanges = preview.some(([, { resourcePreviews }]) => resourcePreviews.some(r => r.localChange !== Change.None || r.remoteChange !== Change.None));
const isLastSyncFromCurrentMachine = preview.every(([, { isLastSyncFromCurrentMachine }]) => isLastSyncFromCurrentMachine);
const hasMergesFromAnotherMachine = preview.some(([syncResource, { isLastSyncFromCurrentMachine, resourcePreviews }]) =>
syncResource !== SyncResource.GlobalState && !isLastSyncFromCurrentMachine
&& resourcePreviews.some(r => r.localChange !== Change.None || r.remoteChange !== Change.None));
action = await this.getFirstTimeSyncAction(hasRemoteData, hasLocalData, hasChanges, isLastSyncFromCurrentMachine);
action = await this.getFirstTimeSyncAction(hasRemoteData, hasLocalData, hasMergesFromAnotherMachine);
const progressDisposable = manualSyncTask.onSynchronizeResources(synchronizingResources =>
synchronizingResources.length ? progress.report({ message: localize('syncing resource', "Syncing {0}...", getSyncAreaLabel(synchronizingResources[0][0])) }) : undefined);
try {
switch (action) {
case 'merge': return await manualSyncTask.apply();
case 'merge':
await manualSyncTask.merge();
if (manualSyncTask.status !== SyncStatus.HasConflicts) {
await manualSyncTask.apply();
}
return;
case 'pull': return await manualSyncTask.pull();
case 'push': return await manualSyncTask.push();
case 'manual': return;
@@ -279,8 +330,15 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
progressDisposable.dispose();
}
});
if (manualSyncTask.status === SyncStatus.HasConflicts) {
await this.dialogService.show(Severity.Warning, localize('conflicts detected', "Conflicts Detected"), [], {
detail: localize('resolve', "Unable to merge due to conflicts. Please merge manually to continue...")
});
await manualSyncTask.discardConflicts();
action = 'manual';
}
if (action === 'manual') {
await this.syncManually(manualSyncTask, preview);
await this.syncManually(manualSyncTask);
}
} catch (error) {
await manualSyncTask.stop();
@@ -290,12 +348,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
}
}
private async getFirstTimeSyncAction(hasRemoteData: boolean, hasLocalData: boolean, hasChanges: boolean, isLastSyncFromCurrentMachine: boolean): Promise<FirstTimeSyncAction> {
private async getFirstTimeSyncAction(hasRemoteData: boolean, hasLocalData: boolean, hasMergesFromAnotherMachine: boolean): Promise<FirstTimeSyncAction> {
if (!hasLocalData /* no data on local */
|| !hasRemoteData /* no data on remote */
|| !hasChanges /* no changes */
|| isLastSyncFromCurrentMachine /* has changes but last sync is from current machine */
|| !hasMergesFromAnotherMachine /* no merges with another machine */
) {
return 'merge';
}
@@ -329,8 +386,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
throw canceled();
}
private async syncManually(task: IManualSyncTask, preview: [SyncResource, ISyncResourcePreview][]): Promise<void> {
private async syncManually(task: IManualSyncTask): Promise<void> {
const visibleViewContainer = this.viewsService.getVisibleViewContainer(ViewContainerLocation.Sidebar);
const preview = await task.preview();
this.userDataSyncPreview.setManualSyncPreview(task, preview);
this.mergesViewEnablementContext.set(true);
@@ -371,6 +429,30 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
await this.viewsService.openViewContainer(SYNC_VIEW_CONTAINER_ID);
}
async switchSyncService(type: UserDataSyncStoreType): Promise<void> {
if (!this.userDataSyncStoreManagementService.userDataSyncStore
|| !this.userDataSyncStoreManagementService.userDataSyncStore.insidersUrl
|| !this.userDataSyncStoreManagementService.userDataSyncStore.stableUrl) {
return;
}
await this.userDataSyncStoreManagementService.switch(type);
const res = await this.dialogService.confirm({
type: 'info',
message: isNative ?
localize('relaunchMessage', "Switching settings sync service requires a restart to take effect.") :
localize('relaunchMessageWeb', "Switching settings sync service requires a reload to take effect."),
detail: isNative ?
localize('relaunchDetail', "Press the restart button to restart {0} and switch.", this.productService.nameLong) :
localize('relaunchDetailWeb', "Press the reload button to reload {0} and switch.", this.productService.nameLong),
primaryButton: isNative ?
localize('restart', "&&Restart") :
localize('restartWeb', "&&Reload"),
});
if (res.confirmed) {
this.hostService.restart();
}
}
private async waitForActiveSyncViews(): Promise<void> {
const viewContainer = this.viewDescriptorService.getViewContainerById(SYNC_VIEW_CONTAINER_ID);
if (viewContainer) {
@@ -381,10 +463,6 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
}
}
private isSupportedAuthenticationProviderId(authenticationProviderId: string): boolean {
return this.authenticationProviders.some(({ id }) => id === authenticationProviderId);
}
private isCurrentAccount(account: UserDataSyncAccount): boolean {
return account.sessionId === this.currentSessionId;
}
@@ -414,15 +492,15 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
}
private async doPick(): Promise<UserDataSyncAccount | IAuthenticationProvider | undefined> {
if (this.authenticationProviders.length === 0) {
if (this.availableAuthenticationProviders.length === 0) {
return undefined;
}
await this.update();
// Single auth provider and no accounts available
if (this.authenticationProviders.length === 1 && !this.all.length) {
return this.authenticationProviders[0];
if (this.availableAuthenticationProviders.length === 1 && !this.all.length) {
return this.availableAuthenticationProviders[0];
}
return new Promise<UserDataSyncAccount | IAuthenticationProvider | undefined>(async (c, e) => {
@@ -431,7 +509,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
const quickPick = this.quickInputService.createQuickPick<AccountQuickPickItem>();
disposables.add(quickPick);
quickPick.title = localize('pick an account', "Preferences Sync");
quickPick.title = SYNC_TITLE;
quickPick.ok = false;
quickPick.placeholder = localize('choose account placeholder', "Select an account");
quickPick.ignoreFocusOut = true;
@@ -454,7 +532,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
// Signed in Accounts
if (this.all.length) {
const authenticationProviders = [...this.authenticationProviders].sort(({ id }) => id === this.current?.authenticationProviderId ? -1 : 1);
const authenticationProviders = [...this.availableAuthenticationProviders].sort(({ id }) => id === this.current?.authenticationProviderId ? -1 : 1);
quickPickItems.push({ type: 'separator', label: localize('signed in', "Signed in") });
for (const authenticationProvider of authenticationProviders) {
const accounts = (this._all.get(authenticationProvider.id) || []).sort(({ sessionId }) => sessionId === this.current?.sessionId ? -1 : 1);
@@ -472,7 +550,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
}
// Account proviers
for (const authenticationProvider of this.authenticationProviders) {
for (const authenticationProvider of this.availableAuthenticationProviders) {
const signedInForProvider = this.all.some(account => account.authenticationProviderId === authenticationProvider.id);
if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) {
const providerName = this.authenticationService.getLabel(authenticationProvider.id);
@@ -500,7 +578,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
this.notificationService.notify({
severity: Severity.Error,
message: localize('successive auth failures', "Preferences sync was turned off because of successive authorization failures. Please sign in again to continue synchronizing"),
message: localize('successive auth failures', "Settings sync was turned off because of successive authorization failures. Please sign in again to continue synchronizing"),
actions: {
primary: [new Action('sign in', localize('sign in', "Sign in"), undefined, true, () => this.signIn())]
}
@@ -593,7 +671,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
this.updateResources();
}
async accept(syncResource: SyncResource, resource: URI, content: string | null): Promise<void> {
async accept(syncResource: SyncResource, resource: URI, content?: string | null): Promise<void> {
if (this.manualSync) {
const syncPreview = await this.manualSync.task.accept(resource, content);
this.updatePreview(syncPreview);

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IAuthenticationProvider, SyncStatus, SyncResource, Change, MergeState } from 'vs/platform/userDataSync/common/userDataSync';
import { IAuthenticationProvider, SyncStatus, SyncResource, Change, MergeState, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { localize } from 'vs/nls';
@@ -20,7 +20,7 @@ export interface IUserDataSyncPreview {
readonly onDidChangeResources: Event<ReadonlyArray<IUserDataSyncResource>>;
readonly resources: ReadonlyArray<IUserDataSyncResource>;
accept(syncResource: SyncResource, resource: URI, content: string | null): Promise<void>;
accept(syncResource: SyncResource, resource: URI, content?: string | null): Promise<void>;
merge(resource?: URI): Promise<void>;
discard(resource?: URI): Promise<void>;
pull(): Promise<void>;
@@ -44,7 +44,9 @@ export const IUserDataSyncWorkbenchService = createDecorator<IUserDataSyncWorkbe
export interface IUserDataSyncWorkbenchService {
_serviceBrand: any;
readonly enabled: boolean;
readonly authenticationProviders: IAuthenticationProvider[];
readonly all: IUserDataSyncAccount[];
readonly current: IUserDataSyncAccount | undefined;
@@ -56,6 +58,7 @@ export interface IUserDataSyncWorkbenchService {
turnOn(): Promise<void>;
turnoff(everyWhere: boolean): Promise<void>;
signIn(): Promise<void>;
switchSyncService(type: UserDataSyncStoreType): Promise<void>;
resetSyncedData(): Promise<void>;
showSyncActivity(): Promise<void>;
@@ -77,6 +80,8 @@ export const enum AccountStatus {
Available = 'available',
}
export const SYNC_TITLE = localize('sync category', "Settings Sync");
// Contexts
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);

View File

@@ -8,6 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { Event } from 'vs/base/common/event';
class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMachinesService {
@@ -15,6 +16,8 @@ class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMac
private readonly channel: IChannel;
get onDidChange(): Event<void> { return this.channel.listen<void>('onDidChange'); }
constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService
) {

View File

@@ -38,6 +38,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>());
readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event;
get onDidResetLocal(): Event<void> { return this.channel.listen<void>('onDidResetLocal'); }
get onDidResetRemote(): Event<void> { return this.channel.listen<void>('onDidResetRemote'); }
constructor(
@ISharedProcessService private readonly sharedProcessService: ISharedProcessService
) {
@@ -65,17 +68,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this._register(this.channel.listen<[SyncResource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)])))));
}
pull(): Promise<void> {
return this.channel.call('pull');
}
createSyncTask(): Promise<ISyncTask> {
throw new Error('not supported');
}
async createManualSyncTask(): Promise<IManualSyncTask> {
const { id, manifest } = await this.channel.call<{ id: string, manifest: IUserDataManifest | null }>('createManualSyncTask');
return new ManualSyncTask(id, manifest, this.sharedProcessService);
const { id, manifest, status } = await this.channel.call<{ id: string, manifest: IUserDataManifest | null, status: SyncStatus }>('createManualSyncTask');
return new ManualSyncTask(id, manifest, status, this.sharedProcessService);
}
replace(uri: URI): Promise<void> {
@@ -120,8 +119,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return handles.map(({ created, uri }) => ({ created, uri: URI.revive(uri) }));
}
async getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
const result = await this.channel.call<{ resource: URI, comparableResource?: URI }[]>('getAssociatedResources', [resource, syncResourceHandle]);
async getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
const result = await this.channel.call<{ resource: URI, comparableResource: URI }[]>('getAssociatedResources', [resource, syncResourceHandle]);
return result.map(({ resource, comparableResource }) => ({ resource: URI.revive(resource), comparableResource: URI.revive(comparableResource) }));
}
@@ -164,16 +163,27 @@ class ManualSyncTask implements IManualSyncTask {
get onSynchronizeResources(): Event<[SyncResource, URI[]][]> { return this.channel.listen<[SyncResource, URI[]][]>('onSynchronizeResources'); }
private _status: SyncStatus;
get status(): SyncStatus { return this._status; }
constructor(
readonly id: string,
readonly manifest: IUserDataManifest | null,
status: SyncStatus,
sharedProcessService: ISharedProcessService,
) {
const manualSyncTaskChannel = sharedProcessService.getChannel(`manualSyncTask-${id}`);
this._status = status;
const that = this;
this.channel = {
call<T>(command: string, arg?: any, cancellationToken?: CancellationToken): Promise<T> {
return manualSyncTaskChannel.call(command, arg, cancellationToken)
.then(null, error => { throw UserDataSyncError.toUserDataSyncError(error); });
async call<T>(command: string, arg?: any, cancellationToken?: CancellationToken): Promise<T> {
try {
const result = await manualSyncTaskChannel.call<T>(command, arg, cancellationToken);
that._status = await manualSyncTaskChannel.call<SyncStatus>('_getStatus');
return result;
} catch (error) {
throw UserDataSyncError.toUserDataSyncError(error);
}
},
listen<T>(event: string, arg?: any): Event<T> {
return manualSyncTaskChannel.listen(event, arg);
@@ -186,12 +196,12 @@ class ManualSyncTask implements IManualSyncTask {
return this.deserializePreviews(previews);
}
async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
async accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('accept', [resource, content]);
return this.deserializePreviews(previews);
}
async merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('merge', [resource]);
return this.deserializePreviews(previews);
}
@@ -201,6 +211,11 @@ class ManualSyncTask implements IManualSyncTask {
return this.deserializePreviews(previews);
}
async discardConflicts(): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('discardConflicts');
return this.deserializePreviews(previews);
}
async apply(): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('apply');
return this.deserializePreviews(previews);