Merge from vscode fcf3346a8e9f5ee1e00674461d9e2c2292a14ee3 (#12295)

* Merge from vscode fcf3346a8e9f5ee1e00674461d9e2c2292a14ee3

* Fix test build break

* Update distro

* Fix build errors

* Update distro

* Update REH build file

* Update build task names for REL

* Fix product build yaml

* Fix product REH task name

* Fix type in task name

* Update linux build step

* Update windows build tasks

* Turn off server publish

* Disable REH

* Fix typo

* Bump distro

* Update vscode tests

* Bump distro

* Fix type in disto

* Bump distro

* Turn off docker build

* Remove docker step from release

Co-authored-by: ADS Merger <andresse@microsoft.com>
Co-authored-by: Karl Burtram <karlb@microsoft.com>
This commit is contained in:
Christopher Suh
2020-10-03 14:42:05 -04:00
committed by GitHub
parent 58d02b76db
commit 6ff1e3866b
687 changed files with 10507 additions and 9104 deletions

View File

@@ -6,6 +6,7 @@
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -15,8 +16,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
interface AccessibilityMetrics {
enabled: boolean;
@@ -74,8 +73,8 @@ registerSingleton(IAccessibilityService, NativeAccessibilityService, true);
class LinuxAccessibilityContribution implements IWorkbenchContribution {
constructor(
@IJSONEditingService jsonEditingService: IJSONEditingService,
@IAccessibilityService accessibilityService: AccessibilityService,
@IEnvironmentService environmentService: IEnvironmentService
@IAccessibilityService accessibilityService: IAccessibilityService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
) {
const forceRendererAccessibility = () => {
if (accessibilityService.isScreenReaderOptimized()) {

View File

@@ -329,7 +329,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
// Activate has already been called for the authentication provider, but it cannot block on registering itself
// since this is sync and returns a disposable. So, wait for registration event to fire that indicates the
// provider is now in the map.
await new Promise((resolve, _) => {
await new Promise<void>((resolve, _) => {
this.onDidRegisterAuthenticationProvider(e => {
if (e.id === providerId) {
provider = this._authenticationProviders.get(providerId);
@@ -444,7 +444,12 @@ export class AuthenticationService extends Disposable implements IAuthentication
const didRegister: Promise<MainThreadAuthenticationProvider> = new Promise((resolve, _) => {
this.onDidRegisterAuthenticationProvider(e => {
if (e.id === providerId) {
resolve(this._authenticationProviders.get(providerId));
provider = this._authenticationProviders.get(providerId);
if (provider) {
resolve(provider);
} else {
throw new Error(`No authentication provider '${providerId}' is currently registered.`);
}
}
});
});

View File

@@ -128,7 +128,7 @@ export class BackupFileService implements IBackupFileService {
}
private initialize(): BackupFileServiceImpl | InMemoryBackupFileService {
const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource;
const backupWorkspaceResource = this.environmentService.backupWorkspaceHome;
if (backupWorkspaceResource) {
return new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService, this.logService);
}
@@ -140,7 +140,7 @@ export class BackupFileService implements IBackupFileService {
// Re-init implementation (unless we are running in-memory)
if (this.impl instanceof BackupFileServiceImpl) {
const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource;
const backupWorkspaceResource = this.environmentService.backupWorkspaceHome;
if (backupWorkspaceResource) {
this.impl.initialize(backupWorkspaceResource);
} else {

View File

@@ -1,12 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { joinPath, relativePath } from 'vs/base/common/resources';
export function toBackupWorkspaceResource(backupWorkspacePath: string, environmentService: INativeEnvironmentService): URI {
return joinPath(environmentService.userRoamingDataHome, relativePath(URI.file(environmentService.userDataPath), URI.file(backupWorkspacePath))!);
}

View File

@@ -24,13 +24,11 @@ import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environ
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
import { BACKUPS } from 'vs/platform/environment/common/environment';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { VSBuffer } from 'vs/base/common/buffer';
import { TestWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice');
const appSettingsHome = path.join(userdataDir, 'User');
const backupHome = path.join(userdataDir, 'Backups');
const workspacesJsonPath = path.join(backupHome, 'workspaces.json');
@@ -46,10 +44,10 @@ const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile));
const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile));
const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile));
class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService {
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
constructor(backupPath: string) {
super({ ...TestWindowConfiguration, backupPath, 'user-data-dir': userdataDir }, TestWindowConfiguration.execPath);
super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': userdataDir });
}
}
@@ -62,12 +60,12 @@ export class NodeTestBackupFileService extends BackupFileService {
discardedBackups: URI[];
constructor(workspaceBackupPath: string) {
const environmentService = new TestBackupEnvironmentService(workspaceBackupPath);
const environmentService = new TestWorkbenchEnvironmentService(workspaceBackupPath);
const logService = new NullLogService();
const fileService = new FileService(logService);
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, logService));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, URI.file(workspaceBackupPath), diskFileSystemProvider, environmentService, logService));
super(environmentService, fileService, logService);
@@ -159,7 +157,7 @@ suite('BackupFileService', () => {
const backupResource = fooFile;
const workspaceHash = hashPath(workspaceResource);
const filePathHash = hashPath(backupResource);
const expectedPath = URI.file(path.join(appSettingsHome, BACKUPS, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString();
const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString();
assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
});
@@ -168,7 +166,7 @@ suite('BackupFileService', () => {
const backupResource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' });
const workspaceHash = hashPath(workspaceResource);
const filePathHash = hashPath(backupResource);
const expectedPath = URI.file(path.join(appSettingsHome, BACKUPS, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString();
const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString();
assert.equal(service.toBackupResource(backupResource).toString(), expectedPath);
});
});

View File

@@ -403,7 +403,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
if (!this.localUserConfiguration.hasTasksLoaded) {
// Reload local user configuration again to load user tasks
runWhenIdle(() => this.reloadLocalUserConfiguration().then(configurationModel => this.onLocalUserConfigurationChanged(configurationModel)), 5000);
runWhenIdle(() => this.reloadLocalUserConfiguration(), 5000);
}
});
}
@@ -436,16 +436,27 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
.then(([local, remote]) => ({ local, remote }));
}
private reloadUserConfiguration(key?: string): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> {
return Promise.all([this.reloadLocalUserConfiguration(), this.reloadRemoeUserConfiguration()]).then(([local, remote]) => ({ local, remote }));
private reloadUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> {
return Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]).then(([local, remote]) => ({ local, remote }));
}
private reloadLocalUserConfiguration(key?: string): Promise<ConfigurationModel> {
return this.localUserConfiguration.reload();
async reloadLocalUserConfiguration(donotTrigger?: boolean): Promise<ConfigurationModel> {
const model = await this.localUserConfiguration.reload();
if (!donotTrigger) {
this.onLocalUserConfigurationChanged(model);
}
return model;
}
private reloadRemoeUserConfiguration(key?: string): Promise<ConfigurationModel> {
return this.remoteUserConfiguration ? this.remoteUserConfiguration.reload() : Promise.resolve(new ConfigurationModel());
private async reloadRemoteUserConfiguration(donotTrigger?: boolean): Promise<ConfigurationModel> {
if (this.remoteUserConfiguration) {
const model = await this.remoteUserConfiguration.reload();
if (!donotTrigger) {
this.onRemoteUserConfigurationChanged(model);
}
return model;
}
return new ConfigurationModel();
}
private reloadWorkspaceConfiguration(key?: string): Promise<void> {
@@ -667,9 +678,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
.then(() => {
switch (editableConfigurationTarget) {
case EditableConfigurationTarget.USER_LOCAL:
return this.reloadLocalUserConfiguration().then(local => this.onLocalUserConfigurationChanged(local));
return this.reloadLocalUserConfiguration().then(() => undefined);
case EditableConfigurationTarget.USER_REMOTE:
return this.reloadRemoeUserConfiguration().then(remote => this.onRemoteUserConfigurationChanged(remote));
return this.reloadRemoteUserConfiguration().then(() => undefined);
case EditableConfigurationTarget.WORKSPACE:
return this.reloadWorkspaceConfiguration();
case EditableConfigurationTarget.WORKSPACE_FOLDER:

View File

@@ -7,7 +7,6 @@ import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
import * as json from 'vs/base/common/json';
import * as strings from 'vs/base/common/strings';
import { setProperty } from 'vs/base/common/jsonEdit';
import { Queue } from 'vs/base/common/async';
import { Edit } from 'vs/base/common/jsonFormatter';
@@ -426,7 +425,7 @@ export class ConfigurationEditingService {
// Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify
if (!jsonPath.length) {
const content = JSON.stringify(value, null, insertSpaces ? strings.repeat(' ', tabSize) : '\t');
const content = JSON.stringify(value, null, insertSpaces ? ' '.repeat(tabSize) : '\t');
return [{
content,
length: model.getValue().length,

View File

@@ -6,7 +6,6 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import * as json from 'vs/base/common/json';
import * as strings from 'vs/base/common/strings';
import { setProperty } from 'vs/base/common/jsonEdit';
import { Queue } from 'vs/base/common/async';
import { Edit } from 'vs/base/common/jsonFormatter';
@@ -79,7 +78,7 @@ export class JSONEditingService implements IJSONEditingService {
// With empty path the entire file is being replaced, so we just use JSON.stringify
if (!path.length) {
const content = JSON.stringify(value, null, insertSpaces ? strings.repeat(' ', tabSize) : '\t');
const content = JSON.stringify(value, null, insertSpaces ? ' '.repeat(tabSize) : '\t');
return [{
content,
length: content.length,

View File

@@ -5,14 +5,14 @@
import * as pfs from 'vs/base/node/pfs';
import { join } from 'vs/base/common/path';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration';
export class ConfigurationCache implements IConfigurationCache {
private readonly cachedConfigurations: Map<string, CachedConfiguration> = new Map<string, CachedConfiguration>();
constructor(private readonly environmentService: INativeEnvironmentService) {
constructor(private readonly environmentService: INativeWorkbenchEnvironmentService) {
}
read(key: ConfigurationKey): Promise<string> {
@@ -47,7 +47,7 @@ class CachedConfiguration {
constructor(
{ type, key }: ConfigurationKey,
environmentService: INativeEnvironmentService
environmentService: INativeWorkbenchEnvironmentService
) {
this.cachedConfigurationFolderPath = join(environmentService.userDataPath, 'CachedConfigurations', type, key);
this.cachedConfigurationFilePath = join(this.cachedConfigurationFolderPath, type === 'workspaces' ? 'workspace.json' : 'configuration.json');

View File

@@ -13,7 +13,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestWindowConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestWorkbenchConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import * as uuid from 'vs/base/common/uuid';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
@@ -37,15 +37,15 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { IFileService } from 'vs/platform/files/common/files';
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-browser/configurationCache';
import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
class TestEnvironmentService extends NativeWorkbenchEnvironmentService {
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
constructor(private _appSettingsHome: URI) {
super(TestWindowConfiguration, TestWindowConfiguration.execPath);
super(TestWorkbenchConfiguration);
}
get appSettingsHome() { return this._appSettingsHome; }
@@ -104,13 +104,13 @@ suite('ConfigurationEditingService', () => {
clearServices();
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(workspaceDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(workspaceDir));
instantiationService.stub(IEnvironmentService, environmentService);
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
const fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService()));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService);

View File

@@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { workbenchInstantiationService, RemoteFileSystemProvider } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestWindowConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestWorkbenchConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -38,7 +38,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
import { FileService } from 'vs/platform/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-browser/configurationCache';
import { ConfigurationCache as BrowserConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration';
@@ -53,10 +53,10 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import product from 'vs/platform/product/common/product';
import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
class TestEnvironmentService extends NativeWorkbenchEnvironmentService {
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
constructor(private _appSettingsHome: URI) {
super(TestWindowConfiguration, TestWindowConfiguration.execPath);
super(TestWorkbenchConfiguration);
}
get appSettingsHome() { return this._appSettingsHome; }
@@ -109,11 +109,11 @@ suite.skip('WorkspaceContextService - Folder', () => { // {{SQL CARBON EDIT}} sk
.then(({ parentDir, folderDir }) => {
parentResource = parentDir;
workspaceResource = folderDir;
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir));
const fileService = new FileService(new NullLogService());
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()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, new DiskFileSystemProvider(new NullLogService()), environmentService, new NullLogService()));
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)));
});
@@ -173,13 +173,13 @@ suite.skip('WorkspaceContextService - Workspace', () => { // {{SQL CARBON EDIT}}
parentResource = parentDir;
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir));
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
const fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService()));
const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService);
instantiationService.stub(IWorkspaceContextService, workspaceService);
@@ -233,13 +233,13 @@ suite.skip('WorkspaceContextService - Workspace Editing', () => { // {{SQL CARBO
parentResource = parentDir;
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir));
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
const fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService()));
const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService);
instantiationService.stub(IWorkspaceContextService, workspaceService);
@@ -494,13 +494,13 @@ suite.skip('WorkspaceService - Initialization', () => { // {{SQL CARBON EDIT}} s
globalSettingsFile = path.join(parentDir, 'settings.json');
const instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir));
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
const fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService()));
const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService);
instantiationService.stub(IWorkspaceContextService, workspaceService);
instantiationService.stub(IConfigurationService, workspaceService);
@@ -771,13 +771,13 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI
globalTasksFile = path.join(parentDir, 'tasks.json');
const instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir));
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService()));
workspaceService = disposableStore.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService));
instantiationService.stub(IWorkspaceContextService, workspaceService);
instantiationService.stub(IConfigurationService, workspaceService);
@@ -1191,14 +1191,14 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI
test('change event when there are global tasks', () => {
fs.writeFileSync(globalTasksFile, '{ "version": "1.0.0", "tasks": [{ "taskName": "myTask" }');
return new Promise((c) => testObject.onDidChangeConfiguration(() => c()));
return new Promise<void>((c) => testObject.onDidChangeConfiguration(() => c()));
});
test('creating workspace settings', async () => {
fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.testSetting": "userValue" }');
await testObject.reloadConfiguration();
const workspaceSettingsResource = URI.file(path.join(workspaceDir, '.vscode', 'settings.json'));
await new Promise(async (c) => {
await new Promise<void>(async (c) => {
const disposable = testObject.onDidChangeConfiguration(e => {
assert.ok(e.affectsConfiguration('configurationService.folder.testSetting'));
assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'workspaceValue');
@@ -1217,7 +1217,7 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI
const workspaceSettingsResource = URI.file(path.join(workspaceDir, '.vscode', 'settings.json'));
await fileService.writeFile(workspaceSettingsResource, VSBuffer.fromString('{ "configurationService.folder.testSetting": "workspaceValue" }'));
await testObject.reloadConfiguration();
await new Promise(async (c) => {
await new Promise<void>(async (c) => {
const disposable = testObject.onDidChangeConfiguration(e => {
assert.ok(e.affectsConfiguration('configurationService.folder.testSetting'));
assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'userValue');
@@ -1280,13 +1280,13 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED
globalSettingsFile = path.join(parentDir, 'settings.json');
const instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir));
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
const fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService()));
const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService);
instantiationService.stub(IWorkspaceContextService, workspaceService);
@@ -1822,7 +1822,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED
await workspaceService.removeFolders([uri]);
fs.writeFileSync(path.join(uri.fsPath, '.vscode', 'settings.json'), '{ "configurationService.workspace.testResourceSetting": "workspaceFolderValue" }');
return new Promise((c, e) => {
return new Promise<void>((c, e) => {
testObject.onDidChangeConfiguration(() => {
try {
assert.equal(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: uri }), 'workspaceFolderValue');
@@ -1883,12 +1883,12 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
remoteSettingsResource = URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority });
instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(parentDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir));
const remoteEnvironmentPromise = new Promise<Partial<IRemoteAgentEnvironment>>(c => resolveRemoteEnvironment = () => c({ settingsPath: remoteSettingsResource }));
const remoteAgentService = instantiationService.stub(IRemoteAgentService, <Partial<IRemoteAgentService>>{ getEnvironment: () => remoteEnvironmentPromise });
const fileService = new FileService(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService()));
const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() };
testObject = new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService);
instantiationService.stub(IWorkspaceContextService, testObject);
@@ -1948,7 +1948,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
registerRemoteFileSystemProvider();
await initialize();
const promise = new Promise((c, e) => {
const promise = new Promise<void>((c, e) => {
testObject.onDidChangeConfiguration(event => {
try {
assert.equal(event.source, ConfigurationTarget.USER);
@@ -1968,7 +1968,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
registerRemoteFileSystemProviderOnActivation();
await initialize();
const promise = new Promise((c, e) => {
const promise = new Promise<void>((c, e) => {
testObject.onDidChangeConfiguration(event => {
try {
assert.equal(event.source, ConfigurationTarget.USER);
@@ -1989,7 +1989,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
resolveRemoteEnvironment();
await initialize();
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet');
const promise = new Promise((c, e) => {
const promise = new Promise<void>((c, e) => {
testObject.onDidChangeConfiguration(event => {
try {
assert.equal(event.source, ConfigurationTarget.USER);

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -13,7 +14,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
export class ConfigurationResolverService extends BaseConfigurationResolverService {

View File

@@ -13,7 +13,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati
import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import { Workspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { Disposable } from 'vs/base/common/lifecycle';
import { IQuickInputService, IQuickPickItem, QuickPickInput, IPickOptions, Omit, IInputOptions, IQuickInputButton, IQuickPick, IInputBox, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput';
@@ -691,6 +691,6 @@ class MockInputsConfigurationService extends TestConfigurationService {
class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
constructor(public userEnv: platform.IProcessEnvironment) {
super({ ...TestWindowConfiguration, userEnv }, TestWindowConfiguration.execPath);
super({ ...TestWorkbenchConfiguration, userEnv });
}
}

View File

@@ -6,7 +6,6 @@
import { ICredentialsProvider, ICredentialsService } from 'vs/platform/credentials/common/credentials';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { find } from 'vs/base/common/arrays';
export class BrowserCredentialsService implements ICredentialsService {
@@ -80,7 +79,7 @@ class InMemoryCredentialsProvider implements ICredentialsProvider {
}
private doFindPassword(service: string, account?: string): ICredential | undefined {
return find(this.credentials, credential =>
return this.credentials.find(credential =>
credential.service === service && (typeof account !== 'string' || credential.account === account));
}

View File

@@ -94,7 +94,7 @@ suite('DecorationsService', function () {
// un-register -> ensure good event
let didSeeEvent = false;
let p = new Promise(resolve => {
let p = new Promise<void>(resolve => {
service.onDidChangeDecorations(e => {
assert.equal(e.affectsResource(uri), true);
assert.deepEqual(service.getDecoration(uri, false), undefined);
@@ -275,7 +275,7 @@ suite('DecorationsService', function () {
data = service.getDecoration(uri2, true)!;
assert.ok(data.tooltip); // emphazied items...
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
let l = service.onDidChangeDecorations(e => {
l.dispose();
try {

View File

@@ -10,12 +10,10 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import { IInstantiationService, } from 'vs/platform/instantiation/common/instantiation';
import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog';
import { WORKSPACE_EXTENSION, isUntitledWorkspace, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { IOpenerService } from 'vs/platform/opener/common/opener';
@@ -25,6 +23,7 @@ import { coalesce } from 'vs/base/common/arrays';
import { trim } from 'vs/base/common/strings';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ILabelService } from 'vs/platform/label/common/label';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
export abstract class AbstractFileDialogService implements IFileDialogService {
@@ -42,7 +41,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
@IDialogService private readonly dialogService: IDialogService,
@IModeService private readonly modeService: IModeService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@ILabelService private readonly labelService: ILabelService
@ILabelService private readonly labelService: ILabelService,
@IPathService private readonly pathService: IPathService
) { }
defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
@@ -230,7 +230,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
}
protected getSchemeFilterForWindow(defaultUriScheme?: string): string {
return !this.environmentService.configuration.remoteAuthority ? (!defaultUriScheme || defaultUriScheme === Schemas.file ? Schemas.file : defaultUriScheme) : REMOTE_HOST_SCHEME;
return defaultUriScheme ?? this.pathService.defaultUriScheme;
}
protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string {

View File

@@ -11,7 +11,6 @@ import { IQuickInputService, IQuickPickItem, IQuickPick } from 'vs/platform/quic
import { URI } from 'vs/base/common/uri';
import { isWindows, OperatingSystem } from 'vs/base/common/platform';
import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -21,12 +20,11 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { Schemas } from 'vs/base/common/network';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { isValidBasename } from 'vs/base/common/extpath';
import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys';
import { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async';
@@ -98,6 +96,8 @@ enum UpdateResult {
InvalidPath
}
export const RemoteFileDialogContext = new RawContextKey<boolean>('remoteFileDialogVisible', false);
export class SimpleFileDialog {
private options!: IOpenDialogOptions;
private currentFolder!: URI;
@@ -108,7 +108,7 @@ export class SimpleFileDialog {
private remoteAuthority: string | undefined;
private requiresTrailing: boolean = false;
private trailing: string | undefined;
protected scheme: string = REMOTE_HOST_SCHEME;
protected scheme: string;
private contextKey: IContextKey<boolean>;
private userEnteredPathSegment: string = '';
private autoCompletePathSegment: string = '';
@@ -141,6 +141,7 @@ export class SimpleFileDialog {
) {
this.remoteAuthority = this.environmentService.configuration.remoteAuthority;
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
this.scheme = this.pathService.defaultUriScheme;
}
set busy(busy: boolean) {
@@ -211,7 +212,7 @@ export class SimpleFileDialog {
path = path.replace(/\\/g, '/');
}
const uri: URI = this.scheme === Schemas.file ? URI.file(path) : URI.from({ scheme: this.scheme, path });
return resources.toLocalResource(uri, uri.scheme === Schemas.file ? undefined : this.remoteAuthority);
return resources.toLocalResource(uri, uri.scheme === Schemas.file ? undefined : this.remoteAuthority, this.pathService.defaultUriScheme);
}
private getScheme(available: readonly string[] | undefined, defaultUri: URI | undefined): string {

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as os from 'os';
import Severity from 'vs/base/common/severity';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
@@ -12,8 +11,6 @@ import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, ISh
import { DialogService as HTMLDialogService } from 'vs/workbench/services/dialogs/browser/dialogService';
import { ILogService } from 'vs/platform/log/common/log';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -23,6 +20,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { MessageBoxOptions } from 'vs/base/parts/sandbox/common/electronTypes';
import { fromNow } from 'vs/base/common/date';
import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
interface IMassagedMessageBoxOptions {
@@ -51,14 +49,13 @@ export class DialogService implements IDialogService {
@ILogService logService: ILogService,
@ILayoutService layoutService: ILayoutService,
@IThemeService themeService: IThemeService,
@ISharedProcessService sharedProcessService: ISharedProcessService,
@IKeybindingService keybindingService: IKeybindingService,
@IProductService productService: IProductService,
@IClipboardService clipboardService: IClipboardService,
@IElectronService electronService: IElectronService
) {
this.customImpl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService);
this.nativeImpl = new NativeDialogService(logService, sharedProcessService, electronService, productService, clipboardService);
this.nativeImpl = new NativeDialogService(logService, electronService, productService, clipboardService);
}
private get useCustomDialog(): boolean {
@@ -92,12 +89,10 @@ class NativeDialogService implements IDialogService {
constructor(
@ILogService private readonly logService: ILogService,
@ISharedProcessService sharedProcessService: ISharedProcessService,
@IElectronService private readonly electronService: IElectronService,
@IProductService private readonly productService: IProductService,
@IClipboardService private readonly clipboardService: IClipboardService
) {
sharedProcessService.registerChannel('dialog', new DialogChannel(this));
}
async confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
@@ -217,6 +212,7 @@ class NativeDialogService implements IDialogService {
}
const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION;
const os = await this.electronService.getOS();
const detailString = (useAgo: boolean): string => {
return nls.localize('aboutDetail',
@@ -228,7 +224,7 @@ class NativeDialogService implements IDialogService {
process.versions['chrome'],
process.versions['node'],
process.versions['v8'],
`${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}`,
`${os.type} ${os.arch} ${os.release}${isSnap ? ' snap' : ''}`,
this.productService.vscodeVersion
);
};

View File

@@ -21,6 +21,7 @@ import { Schemas } from 'vs/base/common/network';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { ILabelService } from 'vs/platform/label/common/label';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
export class FileDialogService extends AbstractFileDialogService implements IFileDialogService {
@@ -39,9 +40,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
@IDialogService dialogService: IDialogService,
@IModeService modeService: IModeService,
@IWorkspacesService workspacesService: IWorkspacesService,
@ILabelService labelService: ILabelService
@ILabelService labelService: ILabelService,
@IPathService pathService: IPathService
) {
super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService, modeService, workspacesService, labelService);
super(hostService, contextService, historyService, environmentService, instantiationService,
configurationService, fileService, openerService, dialogService, modeService, workspacesService, labelService, pathService);
}
private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions {

View File

@@ -436,20 +436,27 @@ export class EditorService extends Disposable implements EditorServiceImpl {
return this.getEditors(EditorsOrder.SEQUENTIAL).map(({ editor }) => editor);
}
getEditors(order: EditorsOrder.MOST_RECENTLY_ACTIVE): ReadonlyArray<IEditorIdentifier>;
getEditors(order: EditorsOrder.SEQUENTIAL, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier>;
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier> {
if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
return this.editorsObserver.editors;
switch (order) {
// MRU
case EditorsOrder.MOST_RECENTLY_ACTIVE:
if (options?.excludeSticky) {
return this.editorsObserver.editors.filter(({ groupId, editor }) => !this.editorGroupService.getGroup(groupId)?.isSticky(editor));
}
return this.editorsObserver.editors;
// Sequential
case EditorsOrder.SEQUENTIAL:
const editors: IEditorIdentifier[] = [];
this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => {
editors.push(...group.getEditors(EditorsOrder.SEQUENTIAL, options).map(editor => ({ editor, groupId: group.id })));
});
return editors;
}
const editors: IEditorIdentifier[] = [];
this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => {
editors.push(...group.getEditors(EditorsOrder.SEQUENTIAL, options).map(editor => ({ editor, groupId: group.id })));
});
return editors;
}
get activeEditor(): IEditorInput | undefined {
@@ -1323,15 +1330,7 @@ export class DelegatingEditorService implements IEditorService {
get editors(): ReadonlyArray<IEditorInput> { return this.editorService.editors; }
get count(): number { return this.editorService.count; }
getEditors(order: EditorsOrder.MOST_RECENTLY_ACTIVE): ReadonlyArray<IEditorIdentifier>;
getEditors(order: EditorsOrder.SEQUENTIAL, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier>;
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier> {
if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
return this.editorService.getEditors(order);
}
return this.editorService.getEditors(order, options);
}
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier> { return this.editorService.getEditors(order, options); }
openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise<IEditorPane[]>;
openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise<IEditorPane[]>;

View File

@@ -62,13 +62,13 @@ export async function openEditorWith(
// Prompt
const resourceExt = extname(resource);
const items: (IQuickPickItem & { handler: IOpenEditorOverrideHandler })[] = allEditorOverrides.map((override) => {
const items: (IQuickPickItem & { handler: IOpenEditorOverrideHandler })[] = allEditorOverrides.map(([handler, entry]) => {
return {
handler: override[0],
id: override[1].id,
label: override[1].label,
description: override[1].active ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined,
detail: override[1].detail,
handler: handler,
id: entry.id,
label: entry.label,
description: entry.active ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined,
detail: entry.detail,
buttons: resourceExt ? [{
iconClass: 'codicon-settings-gear',
tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt)

View File

@@ -175,9 +175,9 @@ export interface IEditorService {
* identifier.
*
* @param order the order of the editors to use
* @param options wether to exclude sticky editors or not
*/
getEditors(order: EditorsOrder.MOST_RECENTLY_ACTIVE): ReadonlyArray<IEditorIdentifier>;
getEditors(order: EditorsOrder.SEQUENTIAL, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier>;
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier>;
/**
* Open an editor in an editor group.

View File

@@ -163,6 +163,11 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite
assert.equal(input, sequentialEditorsExcludingSticky[0].editor);
assert.equal(otherInput, sequentialEditorsExcludingSticky[1].editor);
const mruEditorsExcludingSticky = service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true });
assert.equal(mruEditorsExcludingSticky.length, 2);
assert.equal(input, sequentialEditorsExcludingSticky[0].editor);
assert.equal(otherInput, sequentialEditorsExcludingSticky[1].editor);
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
didCloseEditorListener.dispose();

View File

@@ -7,21 +7,21 @@ import { Schemas } from 'vs/base/common/network';
import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { BACKUPS, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment';
import { IPath } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService, IEnvironmentConfiguration } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
import { IExtensionHostDebugParams } from 'vs/platform/environment/common/environment';
import { IPath, IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkbenchConstructionOptions as IWorkbenchOptions } from 'vs/workbench/workbench.web.api';
import product from 'vs/platform/product/common/product';
import { memoize } from 'vs/base/common/decorators';
import { onUnexpectedError } from 'vs/base/common/errors';
import { parseLineAndColumnAware } from 'vs/base/common/extpath';
import { ColorScheme } from 'vs/platform/theme/common/theme';
export class BrowserEnvironmentConfiguration implements IEnvironmentConfiguration {
class BrowserWorkbenchConfiguration implements IWindowConfiguration {
constructor(
private readonly options: IBrowserWorkbenchEnvironmentConstructionOptions,
private readonly payload: Map<string, string> | undefined,
private readonly backupHome: URI
private readonly options: IBrowserWorkbenchOptions,
private readonly payload: Map<string, string> | undefined
) { }
@memoize
@@ -30,9 +30,6 @@ export class BrowserEnvironmentConfiguration implements IEnvironmentConfiguratio
@memoize
get remoteAuthority(): string | undefined { return this.options.remoteAuthority; }
@memoize
get backupWorkspaceResource(): URI { return joinPath(this.backupHome, this.options.workspaceId); }
@memoize
get filesToOpenOrCreate(): IPath[] | undefined {
if (this.payload) {
@@ -74,12 +71,17 @@ export class BrowserEnvironmentConfiguration implements IEnvironmentConfiguratio
return undefined;
}
get highContrast() {
return false; // could investigate to detect high contrast theme automatically
// TODO@martin TODO@ben this currently does not support high-contrast theme preference (no browser support yet)
get colorScheme() {
if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
return ColorScheme.DARK;
}
return ColorScheme.LIGHT;
}
}
interface IBrowserWorkbenchEnvironmentConstructionOptions extends IWorkbenchConstructionOptions {
interface IBrowserWorkbenchOptions extends IWorkbenchOptions {
workspaceId: string;
logsPath: URI;
}
@@ -96,10 +98,10 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
declare readonly _serviceBrand: undefined;
private _configuration: IEnvironmentConfiguration | undefined = undefined;
get configuration(): IEnvironmentConfiguration {
private _configuration: IWindowConfiguration | undefined = undefined;
get configuration(): IWindowConfiguration {
if (!this._configuration) {
this._configuration = new BrowserEnvironmentConfiguration(this.options, this.payload, this.backupHome);
this._configuration = new BrowserWorkbenchConfiguration(this.options, this.payload);
}
return this._configuration;
@@ -158,7 +160,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
get keyboardLayoutResource(): URI { return joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); }
@memoize
get backupHome(): URI { return joinPath(this.userRoamingDataHome, BACKUPS); }
get backupWorkspaceHome(): URI { return joinPath(this.userRoamingDataHome, 'Backups', this.options.workspaceId); }
@memoize
get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); }
@@ -237,7 +239,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
private payload: Map<string, string> | undefined;
constructor(readonly options: IBrowserWorkbenchEnvironmentConstructionOptions) {
constructor(readonly options: IBrowserWorkbenchOptions) {
if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) {
try {
this.payload = new Map(options.workspaceProvider.payload);

View File

@@ -6,28 +6,42 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
import type { IWorkbenchConstructionOptions as IWorkbenchOptions } from 'vs/workbench/workbench.web.api';
import { URI } from 'vs/base/common/uri';
export const IWorkbenchEnvironmentService = createDecorator<IWorkbenchEnvironmentService>('environmentService');
export interface IEnvironmentConfiguration extends IWindowConfiguration {
backupWorkspaceResource?: URI;
}
/**
* A workbench specific environment service that is only present in workbench
* layer.
*/
export interface IWorkbenchEnvironmentService extends IEnvironmentService {
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH:
// - PUT NON-WEB PROPERTIES INTO NATIVE WB ENV SERVICE
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
readonly _serviceBrand: undefined;
readonly configuration: IEnvironmentConfiguration;
readonly configuration: IWindowConfiguration;
readonly options?: IWorkbenchConstructionOptions;
readonly options?: IWorkbenchOptions;
readonly logFile: URI;
readonly backupWorkspaceHome?: URI;
readonly logExtensionHostCommunication?: boolean;
readonly extensionEnabledProposedApi?: string[];
readonly webviewExternalEndpoint: string;
readonly webviewResourceRoot: string;
readonly webviewCspSource: string;
readonly skipReleaseNotes: boolean;
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH:
// - PUT NON-WEB PROPERTIES INTO NATIVE WB ENV SERVICE
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}

View File

@@ -3,30 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IWorkbenchEnvironmentService, IEnvironmentConfiguration } from 'vs/workbench/services/environment/common/environmentService';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { memoize } from 'vs/base/common/decorators';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup';
import { join } from 'vs/base/common/path';
import { dirname, join } from 'vs/base/common/path';
import product from 'vs/platform/product/common/product';
import { INativeWindowConfiguration } from 'vs/platform/windows/node/window';
export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmentService, INativeEnvironmentService {
readonly configuration: INativeEnvironmentConfiguration;
readonly crashReporterDirectory?: string;
readonly crashReporterId?: string;
readonly cliPath: string;
readonly log?: string;
readonly extHostLogsPath: URI;
}
export interface INativeEnvironmentConfiguration extends IEnvironmentConfiguration, INativeWindowConfiguration { }
import { isLinux, isWindows } from 'vs/base/common/platform';
export class NativeWorkbenchEnvironmentService extends EnvironmentService implements INativeWorkbenchEnvironmentService {
@@ -48,6 +32,9 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem
@memoize
get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); }
// Do not memoize as `backupPath` can change in configuration
get backupWorkspaceHome(): URI | undefined { return this.configuration.backupPath ? URI.file(this.configuration.backupPath).with({ scheme: this.userRoamingDataHome.scheme }) : undefined; }
@memoize
get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); }
@@ -57,12 +44,57 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem
@memoize
get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; }
constructor(
readonly configuration: INativeEnvironmentConfiguration,
execPath: string
) {
super(configuration, execPath);
@memoize
get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; }
this.configuration.backupWorkspaceResource = this.configuration.backupPath ? toBackupWorkspaceResource(this.configuration.backupPath, this) : undefined;
get extensionEnabledProposedApi(): string[] | undefined {
if (Array.isArray(this.args['enable-proposed-api'])) {
return this.args['enable-proposed-api'];
}
if ('enable-proposed-api' in this.args) {
return [];
}
return undefined;
}
@memoize
get cliPath(): string { return this.doGetCLIPath(); }
readonly execPath = this.configuration.execPath;
constructor(
readonly configuration: INativeWorkbenchConfiguration
) {
super(configuration);
}
private doGetCLIPath(): string {
// Windows
if (isWindows) {
if (this.isBuilt) {
return join(dirname(this.execPath), 'bin', `${product.applicationName}.cmd`);
}
return join(this.appRoot, 'scripts', 'code-cli.bat');
}
// Linux
if (isLinux) {
if (this.isBuilt) {
return join(dirname(this.execPath), 'bin', `${product.applicationName}`);
}
return join(this.appRoot, 'scripts', 'code-cli.sh');
}
// macOS
if (this.isBuilt) {
return join(this.appRoot, 'bin', 'code');
}
return join(this.appRoot, 'scripts', 'code-cli.sh');
}
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWindowConfiguration, IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
export interface INativeWorkbenchConfiguration extends IWindowConfiguration, INativeWindowConfiguration { }
/**
* A subclass of the `IWorkbenchEnvironmentService` to be used only in native
* environments (Windows, Linux, macOS) but not e.g. web.
*/
export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmentService, INativeEnvironmentService {
readonly configuration: INativeWorkbenchConfiguration;
readonly crashReporterDirectory?: string;
readonly crashReporterId?: string;
readonly execPath: string;
readonly cliPath: string;
readonly log?: string;
readonly extHostLogsPath: URI;
}

View File

@@ -6,7 +6,7 @@
import { localize } from 'vs/nls';
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { Schemas } from 'vs/base/common/network';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -41,10 +41,9 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
this.remoteExtensionManagementServer = {
id: 'remote',
extensionManagementService,
get label() { return labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); }
get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); }
};
}
if (isWeb) {
} else if (isWeb) {
const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService);
this.webExtensionManagementServer = {
id: 'web',
@@ -55,7 +54,7 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
}
getExtensionManagementServer(extension: IExtension): IExtensionManagementServer {
if (extension.location.scheme === REMOTE_HOST_SCHEME) {
if (extension.location.scheme === Schemas.vscodeRemote) {
return this.remoteExtensionManagementServer!;
}
if (this.webExtensionManagementServer) {

View File

@@ -19,7 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { IGalleryExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
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 type { 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';

View File

@@ -8,7 +8,6 @@ import { Schemas } from 'vs/base/common/network';
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -47,7 +46,7 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
this.remoteExtensionManagementServer = {
id: 'remote',
extensionManagementService,
get label() { return labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); }
get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); }
};
}
}
@@ -56,7 +55,7 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
if (extension.location.scheme === Schemas.file) {
return this.localExtensionManagementServer;
}
if (this.remoteExtensionManagementServer && extension.location.scheme === REMOTE_HOST_SCHEME) {
if (this.remoteExtensionManagementServer && extension.location.scheme === Schemas.vscodeRemote) {
return this.remoteExtensionManagementServer;
}
throw new Error(`Invalid Extension ${extension.location}`);

View File

@@ -18,8 +18,6 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { assign } from 'vs/base/common/objects';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { productService } from 'vs/workbench/test/browser/workbenchTestServices';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
@@ -594,7 +592,7 @@ function anExtensionManagementServerService(localExtensionManagementServer: IExt
if (extension.location.scheme === Schemas.file) {
return localExtensionManagementServer;
}
if (extension.location.scheme === REMOTE_HOST_SCHEME) {
if (extension.location.scheme === Schemas.vscodeRemote) {
return remoteExtensionManagementServer;
}
return webExtensionManagementServer;
@@ -608,11 +606,12 @@ function aLocalExtension(id: string, contributes?: IExtensionContributions, type
function aLocalExtension2(id: string, manifest: any = {}, properties: any = {}): ILocalExtension {
const [publisher, name] = id.split('.');
properties = assign({
manifest = { name, publisher, ...manifest };
properties = {
identifier: { id },
galleryIdentifier: { id, uuid: undefined },
type: ExtensionType.User
}, properties);
manifest = assign({ name, publisher }, manifest);
type: ExtensionType.User,
...properties
};
return <ILocalExtension>Object.create({ manifest, ...properties });
}

View File

@@ -28,6 +28,7 @@ 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';
import { Barrier } from 'vs/base/common/async';
export interface IWebWorkerExtensionHostInitData {
readonly autoStart: boolean;
@@ -82,7 +83,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
return this._protocolPromise;
}
private _startInsideIframe(): Promise<IMessagePassingProtocol> {
private async _startInsideIframe(): Promise<IMessagePassingProtocol> {
const emitter = this._register(new Emitter<VSBuffer>());
const iframe = document.createElement('iframe');
@@ -111,6 +112,9 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
const iframeContent = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
iframe.setAttribute('src', iframeContent);
const barrier = new Barrier();
let port!: MessagePort;
this._register(dom.addDisposableListener(window, 'message', (event) => {
if (event.source !== iframe.contentWindow) {
return;
@@ -129,38 +133,68 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
return;
}
const { data } = event.data;
if (barrier.isOpen() || !(data instanceof MessagePort)) {
console.warn('UNEXPECTED message', event);
this._onDidExit.fire([81, 'UNEXPECTED message']);
return;
}
port = data;
barrier.open();
}));
document.body.appendChild(iframe);
this._register(toDisposable(() => iframe.remove()));
// await MessagePort and use it to directly communicate
// with the worker extension host
await barrier.wait();
port.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)));
}));
};
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]);
port.postMessage(data, [data]);
}
};
document.body.appendChild(iframe);
this._register(toDisposable(() => iframe.remove()));
return this._performHandshake(protocol);
}
private _startOutsideIframe(): Promise<IMessagePassingProtocol> {
private async _startOutsideIframe(): Promise<IMessagePassingProtocol> {
const emitter = new Emitter<VSBuffer>();
const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost');
const worker = new Worker(url, { name: 'WorkerExtensionHost' });
const barrier = new Barrier();
let port!: MessagePort;
worker.onmessage = (event) => {
const { data } = event;
if (barrier.isOpen() || !(data instanceof MessagePort)) {
console.warn('UNEXPECTED message', event);
this._onDidExit.fire([81, 'UNEXPECTED message']);
return;
}
port = data;
barrier.open();
};
// await MessagePort and use it to directly communicate
// with the worker extension host
await barrier.wait();
port.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
@@ -184,7 +218,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
worker.postMessage(data, [data]);
port.postMessage(data, [data]);
}
};

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
export const WEB_WORKER_IFRAME = {
sha: 'sha256-rSINb5Ths99Zj4Ml59jEdHS4WbO+H5Iw+oyRmyi2MLw=',
sha: 'sha256-r24mDVsMuFEo8ChaY9ppVJKbY3CUM4I12Aw/yscWZbg=',
js: `
(function() {
const workerSrc = document.getElementById('vscode-worker-src').getAttribute('data-value');
@@ -13,8 +13,8 @@ export const WEB_WORKER_IFRAME = {
worker.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('Unknown data received', data);
if (!(data instanceof MessagePort)) {
console.warn('Unknown data received', event);
window.parent.postMessage({
vscodeWebWorkerExtHostId,
error: {
@@ -42,16 +42,6 @@ export const WEB_WORKER_IFRAME = {
}
}, '*');
};
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

@@ -14,8 +14,8 @@ import * as platform from 'vs/base/common/platform';
import { originalFSPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IProductService } from 'vs/platform/product/common/productService';

View File

@@ -32,7 +32,6 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints';
import { flatten } from 'vs/base/common/arrays';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
@@ -61,7 +60,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@INotificationService notificationService: INotificationService,
@IWorkbenchEnvironmentService protected readonly _environmentService: INativeWorkbenchEnvironmentService,
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
@ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,
@IFileService fileService: IFileService,

View File

@@ -22,6 +22,7 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { generateRandomPipeName, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
@@ -43,7 +44,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
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';
import { join } from 'vs/base/common/path';
@@ -172,7 +172,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
}
const opts = {
env: env,
env,
// We only detach the extension host on windows. Linux and Mac orphan by default
// and detach under Linux and Mac create another process group.
// We detach because we have noticed that when the renderer exits, its child processes

View File

@@ -33,7 +33,7 @@ self.close = () => console.trace(`'close' has been blocked`);
const nativePostMessage = postMessage.bind(self);
self.postMessage = () => console.trace(`'postMessage' has been blocked`);
const nativeAddEventLister = addEventListener.bind(self);
// const nativeAddEventLister = addEventListener.bind(self);
self.addEventLister = () => console.trace(`'addEventListener' has been blocked`);
(<any>self)['AMDLoader'] = undefined;
@@ -79,11 +79,14 @@ class ExtensionWorker {
constructor() {
let emitter = new Emitter<VSBuffer>();
const channel = new MessageChannel();
const emitter = new Emitter<VSBuffer>();
let terminating = false;
// send over port2, keep port1
nativePostMessage(channel.port2, [channel.port2]);
nativeAddEventLister('message', event => {
channel.port1.onmessage = event => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
@@ -100,14 +103,14 @@ class ExtensionWorker {
// emit non-terminate messages to the outside
emitter.fire(msg);
});
};
this.protocol = {
onMessage: emitter.event,
send: vsbuf => {
if (!terminating) {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
nativePostMessage(data, [data]);
channel.port1.postMessage(data, [data]);
}
}
};

View File

@@ -14,7 +14,8 @@
require.config({
baseUrl: monacoBaseUrl,
catchError: true
catchError: true,
createTrustedScriptURL: (value: string) => value
});
require(['vs/workbench/services/extensions/worker/extensionHostWorker'], () => { }, err => console.error(err));

View File

@@ -35,6 +35,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { extUri } from 'vs/base/common/resources';
import { IdleValue } from 'vs/base/common/async';
import { ResourceGlobMatcher } from 'vs/workbench/common/resources';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
/**
* Stores the selection & view state of an editor and allows to compare it to other selection states.
@@ -117,7 +118,8 @@ export class HistoryService extends Disposable implements IHistoryService {
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IContextKeyService private readonly contextKeyService: IContextKeyService
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IPathService private readonly pathService: IPathService
) {
super();
@@ -514,7 +516,7 @@ export class HistoryService extends Disposable implements IHistoryService {
private preferResourceEditorInput(input: IEditorInput): IEditorInput | IResourceEditorInput {
const resource = input.resource;
if (resource && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) {
if (resource && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData || resource.scheme === this.pathService.defaultUriScheme)) {
// for now, only prefer well known schemes that we control to prevent
// issues such as https://github.com/microsoft/vscode/issues/85204
return { resource };

View File

@@ -84,6 +84,8 @@ export class BrowserHostService extends Disposable implements IHostService {
}
}
//#region Focus
@memoize
get onDidChangeFocus(): Event<boolean> {
const focusTracker = this._register(trackFocus(window));
@@ -107,6 +109,11 @@ export class BrowserHostService extends Disposable implements IHostService {
window.focus();
}
//#endregion
//#region Window
openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;
openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise<void> {
@@ -320,6 +327,10 @@ export class BrowserHostService extends Disposable implements IHostService {
}
}
//#endregion
//#region Lifecycle
async restart(): Promise<void> {
this.reload();
}
@@ -327,6 +338,8 @@ export class BrowserHostService extends Disposable implements IHostService {
async reload(): Promise<void> {
window.location.reload();
}
//#endregion
}
registerSingleton(IHostService, BrowserHostService, true);

View File

@@ -13,6 +13,7 @@ export interface IHostService {
readonly _serviceBrand: undefined;
//#region Focus
/**
@@ -64,7 +65,6 @@ export interface IHostService {
//#endregion
//#region Lifecycle
/**

View File

@@ -24,6 +24,8 @@ export class NativeHostService extends Disposable implements IHostService {
super();
}
//#region Focus
get onDidChangeFocus(): Event<boolean> { return this._onDidChangeFocus; }
private _onDidChangeFocus: Event<boolean> = Event.latch(Event.any(
Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.electronService.windowId), () => this.hasFocus),
@@ -44,6 +46,11 @@ export class NativeHostService extends Disposable implements IHostService {
return activeWindowId === this.electronService.windowId;
}
//#endregion
//#region Window
openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;
openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise<void> {
@@ -82,6 +89,11 @@ export class NativeHostService extends Disposable implements IHostService {
return this.electronService.toggleFullScreen();
}
//#endregion
//#region Lifecycle
focus(options?: { force: boolean }): Promise<void> {
return this.electronService.focusWindow(options);
}
@@ -93,6 +105,8 @@ export class NativeHostService extends Disposable implements IHostService {
reload(): Promise<void> {
return this.electronService.reload();
}
//#endregion
}
registerSingleton(IHostService, NativeHostService, true);

View File

@@ -29,7 +29,12 @@ export interface IHoverService {
* });
* ```
*/
showHover(options: IHoverOptions, focus?: boolean): void;
showHover(options: IHoverOptions, focus?: boolean): IDisposable | undefined;
/**
* Hides the hover if it was visible.
*/
hideHover(): void;
}
export interface IHoverOptions {

View File

@@ -25,9 +25,9 @@ export class HoverService implements IHoverService {
) {
}
showHover(options: IHoverOptions, focus?: boolean): void {
showHover(options: IHoverOptions, focus?: boolean): IDisposable | undefined {
if (this._currentHoverOptions === options) {
return;
return undefined;
}
this._currentHoverOptions = options;
@@ -43,6 +43,16 @@ export class HoverService implements IHoverService {
observer.observe(firstTargetElement);
hover.onDispose(() => observer.disconnect());
}
return hover;
}
hideHover(): void {
if (!this._currentHoverOptions) {
return;
}
this._currentHoverOptions = undefined;
this._contextViewService.hideContextView();
}
private _intersectionChange(entries: IntersectionObserverEntry[], hover: IDisposable): void {

View File

@@ -47,7 +47,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { TestWindowConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { TestWorkbenchConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { ILabelService } from 'vs/platform/label/common/label';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
@@ -61,10 +61,10 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
class TestEnvironmentService extends NativeWorkbenchEnvironmentService {
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
constructor(private _appSettingsHome: URI) {
super(TestWindowConfiguration, TestWindowConfiguration.execPath);
super(TestWorkbenchConfiguration);
}
get appSettingsHome() { return this._appSettingsHome; }
@@ -90,7 +90,7 @@ suite('KeybindingsEditing', () => {
instantiationService = new TestInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(testDir));
const environmentService = new TestWorkbenchEnvironmentService(URI.file(testDir));
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'eol': '\n' });
@@ -117,7 +117,7 @@ suite('KeybindingsEditing', () => {
const fileService = new FileService(new NullLogService());
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService()));
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService()));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService));
instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService());

View File

@@ -21,7 +21,6 @@ import { toLocalISOString } from 'vs/base/common/date';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Emitter, Event } from 'vs/base/common/event';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implements IOutputChannelModel {
@@ -204,7 +203,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IFileService private readonly fileService: IFileService,
@IElectronService private readonly electronService: IElectronService
) {

View File

@@ -9,15 +9,32 @@ import { IPathService, AbstractPathService } from 'vs/workbench/services/path/co
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
export class BrowserPathService extends AbstractPathService {
readonly defaultUriScheme = defaultUriScheme(this.environmentService, this.contextService);
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
) {
super(URI.from({ scheme: Schemas.vscodeRemote, authority: environmentService.configuration.remoteAuthority, path: '/' }), remoteAgentService);
super(URI.from({ scheme: defaultUriScheme(environmentService, contextService), authority: environmentService.configuration.remoteAuthority, path: '/' }), remoteAgentService);
}
}
function defaultUriScheme(environmentService: IWorkbenchEnvironmentService, contextService: IWorkspaceContextService): string {
if (environmentService.configuration.remoteAuthority) {
return Schemas.vscodeRemote;
}
const firstFolder = contextService.getWorkspace().folders[0];
if (!firstFolder) {
throw new Error('Empty workspace is not supported in browser when there is no remote connection.');
}
return firstFolder.uri.scheme;
}
registerSingleton(IPathService, BrowserPathService, true);

View File

@@ -28,6 +28,14 @@ export interface IPathService {
*/
readonly path: Promise<IPath>;
/**
* Determines the best default URI scheme for the current workspace.
* It uses information about whether we're running remote, in browser,
* or native combined with information about the current workspace to
* find the best default scheme.
*/
readonly defaultUriScheme: string;
/**
* Converts the given path to a file URI to use for the target
* environment. If the environment is connected to a remote, it
@@ -60,6 +68,8 @@ export abstract class AbstractPathService implements IPathService {
private resolveUserHome: Promise<URI>;
private maybeUnresolvedUserHome: URI | undefined;
abstract readonly defaultUriScheme: string;
constructor(
private localUserHome: URI,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService

View File

@@ -6,14 +6,17 @@
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IPathService, AbstractPathService } from 'vs/workbench/services/path/common/pathService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { Schemas } from 'vs/base/common/network';
export class NativePathService extends AbstractPathService {
readonly defaultUriScheme = this.environmentService.configuration.remoteAuthority ? Schemas.vscodeRemote : Schemas.file;
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService
@IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService
) {
super(environmentService.userHome, remoteAgentService);
}

View File

@@ -7,7 +7,6 @@ import { Emitter } from 'vs/base/common/event';
import { parse } from 'vs/base/common/json';
import { Disposable } from 'vs/base/common/lifecycle';
import * as network from 'vs/base/common/network';
import { assign } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IPosition } from 'vs/editor/common/core/position';
@@ -346,7 +345,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
if (!options) {
options = { pinned: true };
} else {
options = assign(options, { pinned: true });
options = { ...options, pinned: true };
}
if (openDefaultSettings) {
@@ -368,7 +367,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
if (!options) {
options = { pinned: true };
} else {
options = assign(options, { pinned: true });
options = { ...options, pinned: true };
}
const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget));

View File

@@ -228,9 +228,9 @@ export class ProgressService extends Disposable implements IProgressService {
// Create a promise that we can resolve as needed
// when the outside calls dispose on us
let promiseResolve: () => void;
const promise = new Promise<R>(resolve => promiseResolve = resolve);
const promise = new Promise<void>(resolve => promiseResolve = resolve);
this.withWindowProgress<R>({
this.withWindowProgress({
location: ProgressLocation.Window,
title: options.title ? parseLinkedText(options.title).toString() : undefined, // convert markdown links => string
command: 'notifications.showList'

View File

@@ -12,12 +12,14 @@ import { URI as uri } from 'vs/base/common/uri';
import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDebugParams, IEnvironmentService } from 'vs/platform/environment/common/environment';
import { parseSearchPort, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IDebugParams } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { parseSearchPort } from 'vs/platform/environment/node/environmentService';
import { IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { FileMatch, IFileMatch, IFileQuery, IProgressMessage, IRawSearchService, ISearchComplete, ISearchConfiguration, ISearchProgressItem, ISearchResultProvider, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITextQuery, ISearchService, isFileMatch } from 'vs/workbench/services/search/common/search';
import { SearchChannelClient } from './searchIpc';
import { SearchChannelClient } from 'vs/workbench/services/search/node/searchIpc';
import { SearchService } from 'vs/workbench/services/search/common/searchService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModelService } from 'vs/editor/common/services/modelService';
@@ -34,7 +36,7 @@ export class LocalSearchService extends SearchService {
@ILogService logService: ILogService,
@IExtensionService extensionService: IExtensionService,
@IFileService fileService: IFileService,
@IEnvironmentService readonly environmentService: INativeEnvironmentService,
@IWorkbenchEnvironmentService readonly environmentService: INativeWorkbenchEnvironmentService,
@IInstantiationService readonly instantiationService: IInstantiationService
) {
super(modelService, editorService, telemetryService, logService, extensionService, fileService);

View File

@@ -11,13 +11,13 @@ import * as path from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileMatch, ISearchEngine, ISearchEngineStats, ISearchEngineSuccess, ISearchProgressItem, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, isFileMatch, QueryType } from 'vs/workbench/services/search/common/search';
import { IProgressCallback, SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService';
import { DiskSearch } from 'vs/workbench/services/search/node/searchService';
import { DiskSearch } from 'vs/workbench/services/search/electron-browser/searchService';
const TEST_FOLDER_QUERIES = [
{ folder: URI.file(path.normalize('/some/where')) }
];
const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures'));
const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, '../node/fixtures'));
const MULTIROOT_QUERIES: IFolderQuery[] = [
{ folder: URI.file(path.join(TEST_FIXTURES, 'examples')) },
{ folder: URI.file(path.join(TEST_FIXTURES, 'more')) }

View File

@@ -10,7 +10,7 @@ import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProces
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
export class SharedProcessService implements ISharedProcessService {

View File

@@ -8,6 +8,7 @@ import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IProductService } from 'vs/platform/product/common/productService';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
@@ -17,7 +18,6 @@ import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/wor
import { TelemetryService as BaseTelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
export class TelemetryService extends Disposable implements ITelemetryService {

View File

@@ -312,7 +312,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex
grammarFactory.setTheme(theme, tokenColorMap);
let colorMap = AbstractTextMateService._toColorMap(tokenColorMap);
let cssRules = generateTokensCSSForColorMap(colorMap);
this._styleElement.innerHTML = cssRules;
this._styleElement.textContent = cssRules;
TokenizationRegistry.setColorMap(colorMap);
}

View File

@@ -34,7 +34,6 @@ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/commo
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, UTF8_BOM, detectEncodingByBOMFromBuffer, toEncodeReadable, toDecodeStream, IDecodeStreamResult } from 'vs/workbench/services/textfile/common/encoding';
import { consumeStream } from 'vs/base/common/stream';
import { IModeService } from 'vs/editor/common/services/modeService';
@@ -350,7 +349,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
// path. This can happen if the file was created after the untitled file was opened.
// See https://github.com/Microsoft/vscode/issues/67946
let write: boolean;
if (sourceModel instanceof UntitledTextEditorModel && sourceModel.hasAssociatedFilePath && targetExists && this.uriIdentityService.extUri.isEqual(target, toLocalResource(sourceModel.resource, this.environmentService.configuration.remoteAuthority))) {
if (sourceModel instanceof UntitledTextEditorModel && sourceModel.hasAssociatedFilePath && targetExists && this.uriIdentityService.extUri.isEqual(target, toLocalResource(sourceModel.resource, this.environmentService.configuration.remoteAuthority, this.pathService.defaultUriScheme))) {
write = await this.confirmOverwrite(target);
} else {
write = true;
@@ -431,7 +430,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
// Untitled with associated file path
if (model.hasAssociatedFilePath) {
return toLocalResource(resource, remoteAuthority);
return toLocalResource(resource, remoteAuthority, this.pathService.defaultUriScheme);
}
// Untitled without associated file path: use name
@@ -529,7 +528,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings {
constructor(
@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IFileService private fileService: IFileService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService

View File

@@ -22,13 +22,13 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { ILogService } from 'vs/platform/log/common/log';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IModeService } from 'vs/editor/common/services/modeService';

View File

@@ -1,733 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EncodingMode } from 'vs/workbench/common/editor';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { TextFileEditorModelState, snapshotToString, isTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { createFileEditorInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel } from 'vs/workbench/test/browser/workbenchTestServices';
import { toResource } from 'vs/base/test/common/utils';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { timeout } from 'vs/base/common/async';
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
import { assertIsDefined } from 'vs/base/common/types';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
function getLastModifiedTime(model: TextFileEditorModel): number {
const stat = model.getStat();
return stat ? stat.mtime : -1;
}
suite('Files - TextFileEditorModel', () => {
let instantiationService: IInstantiationService;
let accessor: TestServiceAccessor;
let content: string;
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
content = accessor.fileService.getContent();
});
teardown(() => {
(<TextFileEditorModelManager>accessor.textFileService.files).dispose();
accessor.fileService.setContent(content);
});
test('basic events', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
let onDidLoadCounter = 0;
model.onDidLoad(() => onDidLoadCounter++);
await model.load();
assert.equal(onDidLoadCounter, 1);
let onDidChangeContentCounter = 0;
model.onDidChangeContent(() => onDidChangeContentCounter++);
let onDidChangeDirtyCounter = 0;
model.onDidChangeDirty(() => onDidChangeDirtyCounter++);
model.updateTextEditorModel(createTextBufferFactory('bar'));
assert.equal(onDidChangeContentCounter, 1);
assert.equal(onDidChangeDirtyCounter, 1);
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.equal(onDidChangeContentCounter, 2);
assert.equal(onDidChangeDirtyCounter, 1);
await model.revert();
assert.equal(onDidChangeDirtyCounter, 2);
model.dispose();
});
test('isTextFileEditorModel', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
assert.equal(isTextFileEditorModel(model), true);
model.dispose();
});
test('save', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
assert.equal(accessor.workingCopyService.dirtyCount, 0);
let savedEvent = false;
model.onDidSave(() => savedEvent = true);
await model.save();
assert.ok(!savedEvent);
model.updateTextEditorModel(createTextBufferFactory('bar'));
assert.ok(getLastModifiedTime(model) <= Date.now());
assert.ok(model.hasState(TextFileEditorModelState.DIRTY));
assert.equal(accessor.workingCopyService.dirtyCount, 1);
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
let workingCopyEvent = false;
accessor.workingCopyService.onDidChangeDirty(e => {
if (e.resource.toString() === model.resource.toString()) {
workingCopyEvent = true;
}
});
const pendingSave = model.save();
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
await pendingSave;
assert.ok(model.hasState(TextFileEditorModelState.SAVED));
assert.ok(!model.isDirty());
assert.ok(savedEvent);
assert.ok(workingCopyEvent);
assert.equal(accessor.workingCopyService.dirtyCount, 0);
assert.equal(accessor.workingCopyService.isDirty(model.resource), false);
savedEvent = false;
await model.save({ force: true });
assert.ok(savedEvent);
model.dispose();
assert.ok(!accessor.modelService.getModel(model.resource));
});
test('save - touching also emits saved event', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
let savedEvent = false;
model.onDidSave(() => savedEvent = true);
let workingCopyEvent = false;
accessor.workingCopyService.onDidChangeDirty(e => {
if (e.resource.toString() === model.resource.toString()) {
workingCopyEvent = true;
}
});
await model.save({ force: true });
assert.ok(savedEvent);
assert.ok(!workingCopyEvent);
model.dispose();
assert.ok(!accessor.modelService.getModel(model.resource));
});
test('save - touching with error turns model dirty', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
let saveErrorEvent = false;
model.onDidSaveError(() => saveErrorEvent = true);
let savedEvent = false;
model.onDidSave(() => savedEvent = true);
accessor.fileService.writeShouldThrowError = new Error('failed to write');
try {
await model.save({ force: true });
assert.ok(model.hasState(TextFileEditorModelState.ERROR));
assert.ok(model.isDirty());
assert.ok(saveErrorEvent);
assert.equal(accessor.workingCopyService.dirtyCount, 1);
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
} finally {
accessor.fileService.writeShouldThrowError = undefined;
}
await model.save({ force: true });
assert.ok(savedEvent);
assert.ok(!model.isDirty());
model.dispose();
assert.ok(!accessor.modelService.getModel(model.resource));
});
test('save error (generic)', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
model.updateTextEditorModel(createTextBufferFactory('bar'));
let saveErrorEvent = false;
model.onDidSaveError(() => saveErrorEvent = true);
accessor.fileService.writeShouldThrowError = new Error('failed to write');
try {
const pendingSave = model.save();
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
await pendingSave;
assert.ok(model.hasState(TextFileEditorModelState.ERROR));
assert.ok(model.isDirty());
assert.ok(saveErrorEvent);
assert.equal(accessor.workingCopyService.dirtyCount, 1);
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
model.dispose();
} finally {
accessor.fileService.writeShouldThrowError = undefined;
}
});
test('save error (conflict)', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
model.updateTextEditorModel(createTextBufferFactory('bar'));
let saveErrorEvent = false;
model.onDidSaveError(() => saveErrorEvent = true);
accessor.fileService.writeShouldThrowError = new FileOperationError('save conflict', FileOperationResult.FILE_MODIFIED_SINCE);
try {
const pendingSave = model.save();
assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE));
await pendingSave;
assert.ok(model.hasState(TextFileEditorModelState.CONFLICT));
assert.ok(model.isDirty());
assert.ok(saveErrorEvent);
assert.equal(accessor.workingCopyService.dirtyCount, 1);
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
model.dispose();
} finally {
accessor.fileService.writeShouldThrowError = undefined;
}
});
test('setEncoding - encode', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
let encodingEvent = false;
model.onDidChangeEncoding(() => encodingEvent = true);
model.setEncoding('utf8', EncodingMode.Encode); // no-op
assert.equal(getLastModifiedTime(model), -1);
assert.ok(!encodingEvent);
model.setEncoding('utf16', EncodingMode.Encode);
assert.ok(encodingEvent);
assert.ok(getLastModifiedTime(model) <= Date.now()); // indicates model was saved due to encoding change
model.dispose();
});
test('setEncoding - decode', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.setEncoding('utf16', EncodingMode.Decode);
await timeout(0);
assert.ok(model.isResolved()); // model got loaded due to decoding
model.dispose();
});
test('create with mode', async function () {
const mode = 'text-file-model-test';
ModesRegistry.registerLanguage({
id: mode,
});
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', mode);
await model.load();
assert.equal(model.textEditorModel!.getModeId(), mode);
model.dispose();
assert.ok(!accessor.modelService.getModel(model.resource));
});
test('disposes when underlying model is destroyed', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
model.textEditorModel!.dispose();
assert.ok(model.isDisposed());
});
test('Load does not trigger save', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined);
assert.ok(model.hasState(TextFileEditorModelState.SAVED));
model.onDidSave(() => assert.fail());
model.onDidChangeDirty(() => assert.fail());
await model.load();
assert.ok(model.isResolved());
model.dispose();
assert.ok(!accessor.modelService.getModel(model.resource));
});
test('Load returns dirty model as long as model is dirty', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
assert.ok(model.hasState(TextFileEditorModelState.DIRTY));
await model.load();
assert.ok(model.isDirty());
model.dispose();
});
test('Revert', async function () {
let eventCounter = 0;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.onDidRevert(() => eventCounter++);
let workingCopyEvent = false;
accessor.workingCopyService.onDidChangeDirty(e => {
if (e.resource.toString() === model.resource.toString()) {
workingCopyEvent = true;
}
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
assert.equal(accessor.workingCopyService.dirtyCount, 1);
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
await model.revert();
assert.ok(!model.isDirty());
assert.equal(model.textEditorModel!.getValue(), 'Hello Html');
assert.equal(eventCounter, 1);
assert.ok(workingCopyEvent);
assert.equal(accessor.workingCopyService.dirtyCount, 0);
assert.equal(accessor.workingCopyService.isDirty(model.resource), false);
model.dispose();
});
test('Revert (soft)', async function () {
let eventCounter = 0;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.onDidRevert(() => eventCounter++);
let workingCopyEvent = false;
accessor.workingCopyService.onDidChangeDirty(e => {
if (e.resource.toString() === model.resource.toString()) {
workingCopyEvent = true;
}
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
assert.equal(accessor.workingCopyService.dirtyCount, 1);
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
await model.revert({ soft: true });
assert.ok(!model.isDirty());
assert.equal(model.textEditorModel!.getValue(), 'foo');
assert.equal(eventCounter, 1);
assert.ok(workingCopyEvent);
assert.equal(accessor.workingCopyService.dirtyCount, 0);
assert.equal(accessor.workingCopyService.isDirty(model.resource), false);
model.dispose();
});
test('Undo to saved state turns model non-dirty', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
model.updateTextEditorModel(createTextBufferFactory('Hello Text'));
assert.ok(model.isDirty());
model.textEditorModel!.undo();
assert.ok(!model.isDirty());
});
test('Load and undo turns model dirty', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
accessor.fileService.setContent('Hello Change');
await model.load();
model.textEditorModel!.undo();
assert.ok(model.isDirty());
assert.equal(accessor.workingCopyService.dirtyCount, 1);
assert.equal(accessor.workingCopyService.isDirty(model.resource), true);
});
test('Update Dirty', async function () {
let eventCounter = 0;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.setDirty(true);
assert.ok(!model.isDirty()); // needs to be resolved
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
await model.revert({ soft: true });
assert.ok(!model.isDirty());
model.onDidChangeDirty(() => eventCounter++);
let workingCopyEvent = false;
accessor.workingCopyService.onDidChangeDirty(e => {
if (e.resource.toString() === model.resource.toString()) {
workingCopyEvent = true;
}
});
model.setDirty(true);
assert.ok(model.isDirty());
assert.equal(eventCounter, 1);
assert.ok(workingCopyEvent);
model.setDirty(false);
assert.ok(!model.isDirty());
assert.equal(eventCounter, 2);
model.dispose();
});
test('No Dirty or saving for readonly models', async function () {
let workingCopyEvent = false;
accessor.workingCopyService.onDidChangeDirty(e => {
if (e.resource.toString() === model.resource.toString()) {
workingCopyEvent = true;
}
});
const model = instantiationService.createInstance(TestReadonlyTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
let saveEvent = false;
model.onDidSave(() => {
saveEvent = true;
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(!model.isDirty());
await model.save({ force: true });
assert.equal(saveEvent, false);
await model.revert({ soft: true });
assert.ok(!model.isDirty());
assert.ok(!workingCopyEvent);
model.dispose();
});
test('File not modified error is handled gracefully', async function () {
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
const mtime = getLastModifiedTime(model);
accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_MODIFIED_SINCE));
model = await model.load() as TextFileEditorModel;
assert.ok(model);
assert.equal(getLastModifiedTime(model), mtime);
model.dispose();
});
test('Load error is handled gracefully if model already exists', async function () {
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND));
model = await model.load() as TextFileEditorModel;
assert.ok(model);
model.dispose();
});
test('save() and isDirty() - proper with check for mtimes', async function () {
const input1 = createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async2.txt'));
const input2 = createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async.txt'));
const model1 = await input1.resolve() as TextFileEditorModel;
const model2 = await input2.resolve() as TextFileEditorModel;
model1.updateTextEditorModel(createTextBufferFactory('foo'));
const m1Mtime = assertIsDefined(model1.getStat()).mtime;
const m2Mtime = assertIsDefined(model2.getStat()).mtime;
assert.ok(m1Mtime > 0);
assert.ok(m2Mtime > 0);
assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt')));
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt')));
model2.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt')));
await timeout(10);
await accessor.textFileService.save(toResource.call(this, '/path/index_async.txt'));
await accessor.textFileService.save(toResource.call(this, '/path/index_async2.txt'));
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt')));
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt')));
assert.ok(assertIsDefined(model1.getStat()).mtime > m1Mtime);
assert.ok(assertIsDefined(model2.getStat()).mtime > m2Mtime);
model1.dispose();
model2.dispose();
});
test('Save Participant', async function () {
let eventCounter = 0;
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.onDidSave(() => {
assert.equal(snapshotToString(model.createSnapshot()!), eventCounter === 1 ? 'bar' : 'foobar');
assert.ok(!model.isDirty());
eventCounter++;
});
const participant = accessor.textFileService.files.addSaveParticipant({
participate: async model => {
assert.ok(model.isDirty());
(model as TextFileEditorModel).updateTextEditorModel(createTextBufferFactory('bar'));
assert.ok(model.isDirty());
eventCounter++;
}
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
await model.save();
assert.equal(eventCounter, 2);
participant.dispose();
model.updateTextEditorModel(createTextBufferFactory('foobar'));
assert.ok(model.isDirty());
await model.save();
assert.equal(eventCounter, 3);
model.dispose();
});
test('Save Participant - skip', async function () {
let eventCounter = 0;
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
const participant = accessor.textFileService.files.addSaveParticipant({
participate: async () => {
eventCounter++;
}
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
await model.save({ skipSaveParticipants: true });
assert.equal(eventCounter, 0);
participant.dispose();
model.dispose();
});
test('Save Participant, async participant', async function () {
let eventCounter = 0;
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.onDidSave(() => {
assert.ok(!model.isDirty());
eventCounter++;
});
const participant = accessor.textFileService.files.addSaveParticipant({
participate: model => {
assert.ok(model.isDirty());
(model as TextFileEditorModel).updateTextEditorModel(createTextBufferFactory('bar'));
assert.ok(model.isDirty());
eventCounter++;
return timeout(10);
}
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
const now = Date.now();
await model.save();
assert.equal(eventCounter, 2);
assert.ok(Date.now() - now >= 10);
model.dispose();
participant.dispose();
});
test('Save Participant, bad participant', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
const participant = accessor.textFileService.files.addSaveParticipant({
participate: async () => {
new Error('boom');
}
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
await model.save();
model.dispose();
participant.dispose();
});
test('Save Participant, participant cancelled when saved again', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
let participations: boolean[] = [];
const participant = accessor.textFileService.files.addSaveParticipant({
participate: async (model, context, progress, token) => {
await timeout(10);
if (!token.isCancellationRequested) {
participations.push(true);
}
}
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
const p1 = model.save();
model.updateTextEditorModel(createTextBufferFactory('foo 1'));
const p2 = model.save();
model.updateTextEditorModel(createTextBufferFactory('foo 2'));
const p3 = model.save();
model.updateTextEditorModel(createTextBufferFactory('foo 3'));
const p4 = model.save();
await Promise.all([p1, p2, p3, p4]);
assert.equal(participations.length, 1);
model.dispose();
participant.dispose();
});
test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await testSaveFromSaveParticipant(model, false);
model.dispose();
});
test('Save Participant, calling save from within is unsupported but does not explode (async save)', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await testSaveFromSaveParticipant(model, true);
model.dispose();
});
async function testSaveFromSaveParticipant(model: TextFileEditorModel, async: boolean): Promise<void> {
let savePromise: Promise<boolean>;
let breakLoop = false;
const participant = accessor.textFileService.files.addSaveParticipant({
participate: async model => {
if (breakLoop) {
return;
}
breakLoop = true;
if (async) {
await timeout(10);
}
const newSavePromise = model.save();
// assert that this is the same promise as the outer one
assert.equal(savePromise, newSavePromise);
}
});
await model.load();
model.updateTextEditorModel(createTextBufferFactory('foo'));
savePromise = model.save();
await savePromise;
participant.dispose();
}
});

View File

@@ -1,278 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { workbenchInstantiationService, TestServiceAccessor, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { toResource } from 'vs/base/test/common/utils';
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { extUri } from 'vs/base/common/resources';
import { timeout } from 'vs/base/common/async';
suite('Files - TextFileEditorModelManager', () => {
let instantiationService: IInstantiationService;
let accessor: TestServiceAccessor;
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
});
test('add, remove, clear, get, getAll', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined);
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined);
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined);
manager.add(URI.file('/test.html'), model1);
manager.add(URI.file('/some/other.html'), model2);
manager.add(URI.file('/some/this.txt'), model3);
const fileUpper = URI.file('/TEST.html');
assert(!manager.get(URI.file('foo')));
assert.strictEqual(manager.get(URI.file('/test.html')), model1);
assert.ok(!manager.get(fileUpper));
let results = manager.models;
assert.strictEqual(3, results.length);
let result = manager.get(URI.file('/yes'));
assert.ok(!result);
result = manager.get(URI.file('/some/other.txt'));
assert.ok(!result);
result = manager.get(URI.file('/some/other.html'));
assert.ok(result);
result = manager.get(fileUpper);
assert.ok(!result);
manager.remove(URI.file(''));
results = manager.models;
assert.strictEqual(3, results.length);
manager.remove(URI.file('/some/other.html'));
results = manager.models;
assert.strictEqual(2, results.length);
manager.remove(fileUpper);
results = manager.models;
assert.strictEqual(2, results.length);
manager.clear();
results = manager.models;
assert.strictEqual(0, results.length);
model1.dispose();
model2.dispose();
model3.dispose();
});
test('resolve', async () => {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const resource = URI.file('/test.html');
const encoding = 'utf8';
const events: ITextFileEditorModel[] = [];
const listener = manager.onDidCreate(model => {
events.push(model);
});
const modelPromise = manager.resolve(resource, { encoding });
assert.ok(manager.get(resource)); // model known even before resolved()
const model = await modelPromise;
assert.ok(model);
assert.equal(model.getEncoding(), encoding);
assert.equal(manager.get(resource), model);
const model2 = await manager.resolve(resource, { encoding });
assert.equal(model2, model);
model.dispose();
const model3 = await manager.resolve(resource, { encoding });
assert.notEqual(model3, model2);
assert.equal(manager.get(resource), model3);
model3.dispose();
assert.equal(events.length, 2);
assert.equal(events[0].resource.toString(), model.resource.toString());
assert.equal(events[1].resource.toString(), model2.resource.toString());
listener.dispose();
});
test('removed from cache when model disposed', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined);
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined);
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined);
manager.add(URI.file('/test.html'), model1);
manager.add(URI.file('/some/other.html'), model2);
manager.add(URI.file('/some/this.txt'), model3);
assert.strictEqual(manager.get(URI.file('/test.html')), model1);
model1.dispose();
assert(!manager.get(URI.file('/test.html')));
model2.dispose();
model3.dispose();
});
test('events', async function () {
const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager);
const resource1 = toResource.call(this, '/path/index.txt');
const resource2 = toResource.call(this, '/path/other.txt');
let loadedCounter = 0;
let gotDirtyCounter = 0;
let gotNonDirtyCounter = 0;
let revertedCounter = 0;
let savedCounter = 0;
let encodingCounter = 0;
manager.onDidLoad(({ model }) => {
if (model.resource.toString() === resource1.toString()) {
loadedCounter++;
}
});
manager.onDidChangeDirty(model => {
if (model.resource.toString() === resource1.toString()) {
if (model.isDirty()) {
gotDirtyCounter++;
} else {
gotNonDirtyCounter++;
}
}
});
manager.onDidRevert(model => {
if (model.resource.toString() === resource1.toString()) {
revertedCounter++;
}
});
manager.onDidSave(({ model }) => {
if (model.resource.toString() === resource1.toString()) {
savedCounter++;
}
});
manager.onDidChangeEncoding(model => {
if (model.resource.toString() === resource1.toString()) {
encodingCounter++;
}
});
const model1 = await manager.resolve(resource1, { encoding: 'utf8' });
assert.equal(loadedCounter, 1);
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }], extUri));
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }], extUri));
const model2 = await manager.resolve(resource2, { encoding: 'utf8' });
assert.equal(loadedCounter, 2);
model1.updateTextEditorModel(createTextBufferFactory('changed'));
model1.updatePreferredEncoding('utf16');
await model1.revert();
model1.updateTextEditorModel(createTextBufferFactory('changed again'));
await model1.save();
model1.dispose();
model2.dispose();
await model1.revert();
assert.equal(gotDirtyCounter, 2);
assert.equal(gotNonDirtyCounter, 2);
assert.equal(revertedCounter, 1);
assert.equal(savedCounter, 1);
assert.equal(encodingCounter, 2);
model1.dispose();
model2.dispose();
assert.ok(!accessor.modelService.getModel(resource1));
assert.ok(!accessor.modelService.getModel(resource2));
});
test('disposing model takes it out of the manager', async function () {
const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager);
const resource = toResource.call(this, '/path/index_something.txt');
const model = await manager.resolve(resource, { encoding: 'utf8' });
model.dispose();
assert.ok(!manager.get(resource));
assert.ok(!accessor.modelService.getModel(model.resource));
manager.dispose();
});
test('canDispose with dirty model', async function () {
const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager);
const resource = toResource.call(this, '/path/index_something.txt');
const model = await manager.resolve(resource, { encoding: 'utf8' });
model.updateTextEditorModel(createTextBufferFactory('make dirty'));
let canDisposePromise = manager.canDispose(model as TextFileEditorModel);
assert.ok(canDisposePromise instanceof Promise);
let canDispose = false;
(async () => {
canDispose = await canDisposePromise;
})();
assert.equal(canDispose, false);
model.revert({ soft: true });
await timeout(0);
assert.equal(canDispose, true);
let canDispose2 = manager.canDispose(model as TextFileEditorModel);
assert.equal(canDispose2, true);
manager.dispose();
});
test('mode', async function () {
const mode = 'text-file-model-manager-test';
ModesRegistry.registerLanguage({
id: mode,
});
const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager);
const resource = toResource.call(this, '/path/index_something.txt');
let model = await manager.resolve(resource, { mode });
assert.equal(model.textEditorModel!.getModeId(), mode);
model = await manager.resolve(resource, { mode: 'text' });
assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID);
model.dispose();
manager.dispose();
});
});

View File

@@ -1,168 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { workbenchInstantiationService, TestServiceAccessor, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices';
import { toResource } from 'vs/base/test/common/utils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { FileOperation } from 'vs/platform/files/common/files';
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
suite('Files - TextFileService', () => {
let instantiationService: IInstantiationService;
let model: TextFileEditorModel;
let accessor: TestServiceAccessor;
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
});
teardown(() => {
model?.dispose();
(<TestTextFileEditorModelManager>accessor.textFileService.files).dispose();
});
test('isDirty/getDirty - files and untitled', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
assert.ok(!accessor.textFileService.isDirty(model.resource));
model.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
const untitled = await accessor.textFileService.untitled.resolve();
assert.ok(!accessor.textFileService.isDirty(untitled.resource));
untitled.textEditorModel.setValue('changed');
assert.ok(accessor.textFileService.isDirty(untitled.resource));
untitled.dispose();
model.dispose();
});
test('save - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
model.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
const res = await accessor.textFileService.save(model.resource);
assert.equal(res?.toString(), model.resource.toString());
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('saveAll - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
model.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
const res = await accessor.textFileService.save(model.resource);
assert.equal(res?.toString(), model.resource.toString());
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('saveAs - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
accessor.fileDialogService.setPickFileToSave(model.resource);
await model.load();
model.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
const res = await accessor.textFileService.saveAs(model.resource);
assert.equal(res!.toString(), model.resource.toString());
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('revert - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
accessor.fileDialogService.setPickFileToSave(model.resource);
await model.load();
model!.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
await accessor.textFileService.revert(model.resource);
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('create does not overwrite existing model', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
model!.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
let eventCounter = 0;
const disposable1 = accessor.workingCopyFileService.addFileOperationParticipant({
participate: async files => {
assert.equal(files[0].target, model.resource.toString());
eventCounter++;
}
});
const disposable2 = accessor.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => {
assert.equal(e.operation, FileOperation.CREATE);
assert.equal(e.files[0].target.toString(), model.resource.toString());
eventCounter++;
});
await accessor.textFileService.create(model.resource, 'Foo');
assert.ok(!accessor.textFileService.isDirty(model.resource));
assert.equal(eventCounter, 2);
disposable1.dispose();
disposable2.dispose();
});
test('Filename Suggestion - Suggest prefix only when there are no relevant extensions', () => {
ModesRegistry.registerLanguage({
id: 'plumbus0',
extensions: ['.one', '.two']
});
let suggested = accessor.textFileService.suggestFilename('shleem', 'Untitled-1');
assert.equal(suggested, 'Untitled-1');
});
test('Filename Suggestion - Suggest prefix with first extension', () => {
ModesRegistry.registerLanguage({
id: 'plumbus1',
extensions: ['.shleem', '.gazorpazorp'],
filenames: ['plumbus']
});
let suggested = accessor.textFileService.suggestFilename('plumbus1', 'Untitled-1');
assert.equal(suggested, 'Untitled-1.shleem');
});
test('Filename Suggestion - Suggest filename if there are no extensions', () => {
ModesRegistry.registerLanguage({
id: 'plumbus2',
filenames: ['plumbus', 'shleem', 'gazorpazorp']
});
let suggested = accessor.textFileService.suggestFilename('plumbus2', 'Untitled-1');
assert.equal(suggested, 'plumbus');
});
});

View File

@@ -1,212 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITextModel } from 'vs/editor/common/model';
import { URI } from 'vs/base/common/uri';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { workbenchInstantiationService, TestServiceAccessor, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices';
import { toResource } from 'vs/base/test/common/utils';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { Event } from 'vs/base/common/event';
import { timeout } from 'vs/base/common/async';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
suite('Workbench - TextModelResolverService', () => {
let instantiationService: IInstantiationService;
let accessor: TestServiceAccessor;
let model: TextFileEditorModel;
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
});
teardown(() => {
if (model) {
model.dispose();
model = (undefined)!;
}
(<TextFileEditorModelManager>accessor.textFileService.files).dispose();
});
test('resolve resource', async () => {
const dispose = accessor.textModelResolverService.registerTextModelContentProvider('test', {
provideTextContent: function (resource: URI): Promise<ITextModel> {
if (resource.scheme === 'test') {
let modelContent = 'Hello Test';
let languageSelection = accessor.modeService.create('json');
return Promise.resolve(accessor.modelService.createModel(modelContent, languageSelection, resource));
}
return Promise.resolve(null!);
}
});
let resource = URI.from({ scheme: 'test', authority: null!, path: 'thePath' });
let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, resource, 'The Name', 'The Description', undefined);
const model = await input.resolve();
assert.ok(model);
assert.equal(snapshotToString(((model as ResourceEditorModel).createSnapshot()!)), 'Hello Test');
let disposed = false;
let disposedPromise = new Promise(resolve => {
Event.once(model.onDispose)(() => {
disposed = true;
resolve();
});
});
input.dispose();
await disposedPromise;
assert.equal(disposed, true);
dispose.dispose();
});
test('resolve file', async function () {
const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(textModel.resource, textModel);
await textModel.load();
const ref = await accessor.textModelResolverService.createModelReference(textModel.resource);
const model = ref.object;
const editorModel = model.textEditorModel;
assert.ok(editorModel);
assert.equal(editorModel.getValue(), 'Hello Html');
let disposed = false;
Event.once(model.onDispose)(() => {
disposed = true;
});
ref.dispose();
await timeout(0); // due to the reference resolving the model first which is async
assert.equal(disposed, true);
});
test('resolved dirty file eventually disposes', async function () {
const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(textModel.resource, textModel);
const loadedModel = await textModel.load();
loadedModel.updateTextEditorModel(createTextBufferFactory('make dirty'));
const ref = await accessor.textModelResolverService.createModelReference(textModel.resource);
let disposed = false;
Event.once(loadedModel.onDispose)(() => {
disposed = true;
});
ref.dispose();
await timeout(0);
assert.equal(disposed, false); // not disposed because model still dirty
loadedModel.revert();
await timeout(0);
assert.equal(disposed, true); // now disposed because model got reverted
});
test('resolved dirty file does not dispose when new reference created', async function () {
const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(textModel.resource, textModel);
const loadedModel = await textModel.load();
loadedModel.updateTextEditorModel(createTextBufferFactory('make dirty'));
const ref1 = await accessor.textModelResolverService.createModelReference(textModel.resource);
let disposed = false;
Event.once(loadedModel.onDispose)(() => {
disposed = true;
});
ref1.dispose();
await timeout(0);
assert.equal(disposed, false); // not disposed because model still dirty
const ref2 = await accessor.textModelResolverService.createModelReference(textModel.resource);
loadedModel.revert();
await timeout(0);
assert.equal(disposed, false); // not disposed because we got another ref meanwhile
ref2.dispose();
await timeout(0);
assert.equal(disposed, true); // now disposed because last ref got disposed
});
test('resolve untitled', async () => {
const service = accessor.untitledTextEditorService;
const untitledModel = service.create();
const input = instantiationService.createInstance(UntitledTextEditorInput, untitledModel);
await input.resolve();
const ref = await accessor.textModelResolverService.createModelReference(input.resource);
const model = ref.object;
assert.equal(untitledModel, model);
const editorModel = model.textEditorModel;
assert.ok(editorModel);
ref.dispose();
input.dispose();
model.dispose();
});
test('even loading documents should be refcounted', async () => {
let resolveModel!: Function;
let waitForIt = new Promise(c => resolveModel = c);
const disposable = accessor.textModelResolverService.registerTextModelContentProvider('test', {
provideTextContent: async (resource: URI): Promise<ITextModel> => {
await waitForIt;
let modelContent = 'Hello Test';
let languageSelection = accessor.modeService.create('json');
return accessor.modelService.createModel(modelContent, languageSelection, resource);
}
});
const uri = URI.from({ scheme: 'test', authority: null!, path: 'thePath' });
const modelRefPromise1 = accessor.textModelResolverService.createModelReference(uri);
const modelRefPromise2 = accessor.textModelResolverService.createModelReference(uri);
resolveModel();
const modelRef1 = await modelRefPromise1;
const model1 = modelRef1.object;
const modelRef2 = await modelRefPromise2;
const model2 = modelRef2.object;
const textModel = model1.textEditorModel;
assert.equal(model1, model2, 'they are the same model');
assert(!textModel.isDisposed(), 'the text model should not be disposed');
modelRef1.dispose();
assert(!textModel.isDisposed(), 'the text model should still not be disposed');
let p1 = new Promise(resolve => textModel.onWillDispose(resolve));
modelRef2.dispose();
await p1;
assert(textModel.isDisposed(), 'the text model should finally be disposed');
disposable.dispose();
});
});

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService';
import { ColorScheme } from 'vs/platform/theme/common/theme';
export class BrowserHostColorSchemeService extends Disposable implements IHostColorSchemeService {
declare readonly _serviceBrand: undefined;
private readonly _onDidSchemeChangeEvent = this._register(new Emitter<void>());
constructor(
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
window.matchMedia('(prefers-color-scheme: dark)').addListener(() => {
this._onDidSchemeChangeEvent.fire();
});
window.matchMedia('(forced-colors: active)').addListener(() => {
this._onDidSchemeChangeEvent.fire();
});
}
get onDidChangeColorScheme(): Event<void> {
return this._onDidSchemeChangeEvent.event;
}
get colorScheme(): ColorScheme {
if (window.matchMedia(`(forced-colors: active)`).matches) {
return ColorScheme.HIGH_CONTRAST;
} else if (window.matchMedia(`(prefers-color-scheme: light)`).matches) {
return ColorScheme.LIGHT;
} else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
return ColorScheme.DARK;
}
return this.environmentService.configuration.colorScheme;
}
}
registerSingleton(IHostColorSchemeService, BrowserHostColorSchemeService, true);

View File

@@ -377,5 +377,5 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i
return result;
}
function escapeCSS(str: string) {
return (<any>window)['CSS'].escape(str);
return window.CSS.escape(str);
}

View File

@@ -13,7 +13,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import * as errors from 'vs/base/common/errors';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData';
import { IColorTheme, Extensions as ThemingExtensions, IThemingRegistry, ThemeType, LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { IColorTheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
import { Event, Emitter } from 'vs/base/common/event';
import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -34,6 +34,9 @@ import { ProductIconThemeData, DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbenc
import { registerProductIconThemeSchemas } from 'vs/workbench/services/themes/common/productIconThemeSchema';
import { ILogService } from 'vs/platform/log/common/log';
import { isWeb } from 'vs/base/common/platform';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService';
// implementation
@@ -92,7 +95,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
private readonly onProductIconThemeChange: Emitter<IWorkbenchProductIconTheme>;
private readonly productIconThemeWatcher: ThemeFileWatcher;
private isOSInHighContrast: boolean; // tracking the high contrast state of the OS eventauilly should go out to a seperate service
private themeSettingIdBeforeSchemeSwitch: string | undefined;
constructor(
@@ -104,13 +106,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
@IFileService private readonly fileService: IFileService,
@IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService,
@IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService,
@ILogService private readonly logService: ILogService
@ILogService private readonly logService: ILogService,
@IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService,
) {
this.container = layoutService.container;
this.settings = new ThemeConfiguration(configurationService);
this.isOSInHighContrast = !!environmentService.configuration.highContrast;
this.colorThemeRegistry = new ThemeRegistry(extensionService, colorThemesExtPoint, ColorThemeData.fromExtensionTheme);
this.colorThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentColorTheme.bind(this));
this.onColorThemeChange = new Emitter<IWorkbenchColorTheme>({ leakWarningThreshold: 400 });
@@ -144,7 +145,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
}
}
if (!themeData) {
themeData = ColorThemeData.createUnloadedThemeForThemeType(isWeb ? LIGHT : DARK);
themeData = ColorThemeData.createUnloadedThemeForThemeType(isWeb ? ColorScheme.LIGHT : ColorScheme.DARK);
}
themeData.setCustomizations(this.settings);
this.applyTheme(themeData, undefined, true);
@@ -217,14 +218,14 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
if (e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME) || e.affectsConfiguration(ThemeSettings.DETECT_HC)) {
this.handlePreferredSchemeUpdated();
}
if (e.affectsConfiguration(ThemeSettings.PREFERRED_DARK_THEME) && this.getPreferredColorScheme() === DARK) {
this.applyPreferredColorTheme(DARK);
if (e.affectsConfiguration(ThemeSettings.PREFERRED_DARK_THEME) && this.getPreferredColorScheme() === ColorScheme.DARK) {
this.applyPreferredColorTheme(ColorScheme.DARK);
}
if (e.affectsConfiguration(ThemeSettings.PREFERRED_LIGHT_THEME) && this.getPreferredColorScheme() === LIGHT) {
this.applyPreferredColorTheme(LIGHT);
if (e.affectsConfiguration(ThemeSettings.PREFERRED_LIGHT_THEME) && this.getPreferredColorScheme() === ColorScheme.LIGHT) {
this.applyPreferredColorTheme(ColorScheme.LIGHT);
}
if (e.affectsConfiguration(ThemeSettings.PREFERRED_HC_THEME) && this.getPreferredColorScheme() === HIGH_CONTRAST) {
this.applyPreferredColorTheme(HIGH_CONTRAST);
if (e.affectsConfiguration(ThemeSettings.PREFERRED_HC_THEME) && this.getPreferredColorScheme() === ColorScheme.HIGH_CONTRAST) {
this.applyPreferredColorTheme(ColorScheme.HIGH_CONTRAST);
}
if (e.affectsConfiguration(ThemeSettings.FILE_ICON_THEME)) {
this.restoreFileIconTheme();
@@ -325,14 +326,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
// preferred scheme handling
private installPreferredSchemeListener() {
window.matchMedia('(prefers-color-scheme: dark)').addListener(async () => this.handlePreferredSchemeUpdated());
}
public setOSHighContrast(highContrast: boolean): void {
if (this.isOSInHighContrast !== highContrast) {
this.isOSInHighContrast = highContrast;
this.handlePreferredSchemeUpdated();
}
this.hostColorService.onDidChangeColorScheme(() => this.handlePreferredSchemeUpdated());
}
private async handlePreferredSchemeUpdated() {
@@ -357,23 +351,22 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
return undefined;
}
private getPreferredColorScheme(): ThemeType | undefined {
private getPreferredColorScheme(): ColorScheme | undefined {
const detectHCThemeSetting = this.configurationService.getValue<boolean>(ThemeSettings.DETECT_HC);
if (this.isOSInHighContrast && detectHCThemeSetting) {
return HIGH_CONTRAST;
if (this.hostColorService.colorScheme === ColorScheme.HIGH_CONTRAST && detectHCThemeSetting) {
return ColorScheme.HIGH_CONTRAST;
}
if (this.configurationService.getValue<boolean>(ThemeSettings.DETECT_COLOR_SCHEME)) {
if (window.matchMedia(`(prefers-color-scheme: light)`).matches) {
return LIGHT;
} else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
return DARK;
const osScheme = this.hostColorService.colorScheme;
if (osScheme !== ColorScheme.HIGH_CONTRAST) {
return osScheme;
}
}
return undefined;
}
private async applyPreferredColorTheme(type: ThemeType): Promise<IWorkbenchColorTheme | null> {
const settingId = type === DARK ? ThemeSettings.PREFERRED_DARK_THEME : type === LIGHT ? ThemeSettings.PREFERRED_LIGHT_THEME : ThemeSettings.PREFERRED_HC_THEME;
private async applyPreferredColorTheme(type: ColorScheme): Promise<IWorkbenchColorTheme | null> {
const settingId = type === ColorScheme.DARK ? ThemeSettings.PREFERRED_DARK_THEME : type === ColorScheme.LIGHT ? ThemeSettings.PREFERRED_LIGHT_THEME : ThemeSettings.PREFERRED_HC_THEME;
const themeSettingId = this.configurationService.getValue<string>(settingId);
if (themeSettingId) {
const theme = await this.colorThemeRegistry.findThemeBySettingsId(themeSettingId, undefined);
@@ -446,6 +439,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
}
}
};
ruleCollector.addRule(`.monaco-workbench { forced-color-adjust: none; }`);
themingRegistry.getThemingParticipants().forEach(p => p(themeData, ruleCollector, this.environmentService));
_applyRules([...cssRules].join('\n'), colorThemeRulesClassName);
}
@@ -692,10 +686,10 @@ function _applyRules(styleSheetContent: string, rulesClassName: string) {
const elStyle = document.createElement('style');
elStyle.type = 'text/css';
elStyle.className = rulesClassName;
elStyle.innerHTML = styleSheetContent;
elStyle.textContent = styleSheetContent;
document.head.appendChild(elStyle);
} else {
(<HTMLStyleElement>themeStyles[0]).innerHTML = styleSheetContent;
(<HTMLStyleElement>themeStyles[0]).textContent = styleSheetContent;
}
}

View File

@@ -14,7 +14,7 @@ import * as objects from 'vs/base/common/objects';
import * as arrays from 'vs/base/common/arrays';
import * as resources from 'vs/base/common/resources';
import { Extensions as ColorRegistryExtensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { ThemeType, ITokenStyle, getThemeTypeSelector } from 'vs/platform/theme/common/themeService';
import { ITokenStyle, getThemeTypeSelector } from 'vs/platform/theme/common/themeService';
import { Registry } from 'vs/platform/registry/common/platform';
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import { URI } from 'vs/base/common/uri';
@@ -26,6 +26,7 @@ import { IExtensionResourceLoaderService } from 'vs/workbench/services/extension
import { CharCode } from 'vs/base/common/charCode';
import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
import { ThemeConfiguration } from 'vs/workbench/services/themes/common/themeConfiguration';
import { ColorScheme } from 'vs/platform/theme/common/theme';
let colorRegistry = Registry.as<IColorRegistry>(ColorRegistryExtensions.ColorContribution);
@@ -540,17 +541,17 @@ export class ColorThemeData implements IWorkbenchColorTheme {
return this.id.split(' ')[0];
}
get type(): ThemeType {
get type(): ColorScheme {
switch (this.baseTheme) {
case VS_LIGHT_THEME: return 'light';
case VS_HC_THEME: return 'hc';
default: return 'dark';
case VS_LIGHT_THEME: return ColorScheme.LIGHT;
case VS_HC_THEME: return ColorScheme.HIGH_CONTRAST;
default: return ColorScheme.DARK;
}
}
// constructors
static createUnloadedThemeForThemeType(themeType: ThemeType, colorMap?: { [id: string]: string }): ColorThemeData {
static createUnloadedThemeForThemeType(themeType: ColorScheme, colorMap?: { [id: string]: string }): ColorThemeData {
return ColorThemeData.createUnloadedTheme(getThemeTypeSelector(themeType), colorMap);
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ColorScheme } from 'vs/platform/theme/common/theme';
export const IHostColorSchemeService = createDecorator<IHostColorSchemeService>('hostColorSchemeService');
export interface IHostColorSchemeService {
readonly _serviceBrand: undefined;
readonly colorScheme: ColorScheme;
readonly onDidChangeColorScheme: Event<void>;
}

View File

@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { textmateColorsSchemaId, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema';
@@ -96,6 +96,13 @@ const productIconThemeSettingSchema: IConfigurationPropertySchema = {
errorMessage: nls.localize('productIconThemeError', "Product icon theme is unknown or not installed.")
};
const detectHCSchemeSettingSchema: IConfigurationPropertySchema = {
type: 'boolean',
default: true,
description: nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme."),
scope: ConfigurationScope.APPLICATION
};
const themeSettingsConfiguration: IConfigurationNode = {
id: 'workbench',
order: 7.1,
@@ -105,7 +112,6 @@ const themeSettingsConfiguration: IConfigurationNode = {
[ThemeSettings.PREFERRED_DARK_THEME]: preferredDarkThemeSettingSchema,
[ThemeSettings.PREFERRED_LIGHT_THEME]: preferredLightThemeSettingSchema,
[ThemeSettings.PREFERRED_HC_THEME]: preferredHCThemeSettingSchema,
[ThemeSettings.DETECT_COLOR_SCHEME]: detectColorSchemeSettingSchema,
[ThemeSettings.FILE_ICON_THEME]: fileIconThemeSettingSchema,
[ThemeSettings.COLOR_CUSTOMIZATIONS]: colorCustomizationsSchema,
[ThemeSettings.PRODUCT_ICON_THEME]: productIconThemeSettingSchema
@@ -113,6 +119,17 @@ const themeSettingsConfiguration: IConfigurationNode = {
};
configurationRegistry.registerConfiguration(themeSettingsConfiguration);
const themeSettingsWindowConfiguration: IConfigurationNode = {
id: 'window',
order: 8.1,
type: 'object',
properties: {
[ThemeSettings.DETECT_HC]: detectHCSchemeSettingSchema,
[ThemeSettings.DETECT_COLOR_SCHEME]: detectColorSchemeSettingSchema,
}
};
configurationRegistry.registerConfiguration(themeSettingsWindowConfiguration);
function tokenGroupSettings(description: string): IJSONSchema {
return {
description,

View File

@@ -77,8 +77,6 @@ export interface IWorkbenchThemeService extends IThemeService {
getProductIconTheme(): IWorkbenchProductIconTheme;
getProductIconThemes(): Promise<IWorkbenchProductIconTheme[]>;
onDidProductIconThemeChange: Event<IWorkbenchProductIconTheme>;
setOSHighContrast(highContrast: boolean): void;
}
export interface IColorCustomizations {

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { Disposable } from 'vs/base/common/lifecycle';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService';
export class NativeHostColorSchemeService extends Disposable implements IHostColorSchemeService {
declare readonly _serviceBrand: undefined;
constructor(
@IElectronService private readonly electronService: IElectronService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
// Color Scheme
this._register(this.electronService.onColorSchemeChange(scheme => {
this._colorScheme = scheme;
this._onDidChangeColorScheme.fire();
}));
}
private readonly _onDidChangeColorScheme = this._register(new Emitter<void>());
readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event;
private _colorScheme: ColorScheme = this.environmentService.configuration.colorScheme;
get colorScheme() { return this._colorScheme; }
}
registerSingleton(IHostColorSchemeService, NativeHostColorSchemeService, true);

View File

@@ -7,6 +7,7 @@ import { virtualMachineHint } from 'vs/base/node/id';
import * as os from 'os';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IUpdateService } from 'vs/platform/update/common/update';
@@ -15,7 +16,6 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IStartupMetrics, AbstractTimerService, Writeable } from 'vs/workbench/services/timer/browser/timerService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';

View File

@@ -107,19 +107,23 @@ export class UserDataInitializationService implements IUserDataInitializationSer
}
async requiresInitialization(): Promise<boolean> {
this.logService.trace(`UserDataInitializationService#requiresInitialization`);
const userDataSyncStoreClient = await this.createUserDataSyncStoreClient();
return !!userDataSyncStoreClient;
}
async initializeRequiredResources(): Promise<void> {
this.logService.trace(`UserDataInitializationService#initializeRequiredResources`);
return this.initialize([SyncResource.Settings, SyncResource.GlobalState]);
}
async initializeOtherResources(): Promise<void> {
this.logService.trace(`UserDataInitializationService#initializeOtherResources`);
return this.initialize([SyncResource.Keybindings, SyncResource.Snippets]);
}
async initializeExtensions(instantiationService: IInstantiationService): Promise<void> {
this.logService.trace(`UserDataInitializationService#initializeExtensions`);
return this.initialize([SyncResource.Extensions], instantiationService);
}

View File

@@ -7,7 +7,6 @@ import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, hasFileReadStreamCapability } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { BACKUPS } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ReadableStreamEvents } from 'vs/base/common/stream';
@@ -26,11 +25,12 @@ export class FileUserDataProvider extends Disposable implements
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
private readonly userDataHome: URI;
private readonly backupHome: URI | undefined;
private extUri: ExtUri;
constructor(
private readonly fileSystemUserDataHome: URI,
private readonly fileSystemBackupsHome: URI,
private readonly fileSystemBackupsHome: URI | undefined,
private readonly fileSystemProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability,
environmentService: IWorkbenchEnvironmentService,
private readonly logService: ILogService,
@@ -38,6 +38,7 @@ export class FileUserDataProvider extends Disposable implements
super();
this.userDataHome = environmentService.userRoamingDataHome;
this.backupHome = environmentService.backupWorkspaceHome;
this.extUri = !!(this.capabilities & FileSystemProviderCapabilities.PathCaseSensitive) ? extUri : extUriIgnorePathCase;
// update extUri as capabilites might change.
@@ -139,10 +140,13 @@ export class FileUserDataProvider extends Disposable implements
}
private toFileSystemResource(userDataResource: URI): URI {
const relativePath = this.extUri.relativePath(this.userDataHome, userDataResource)!;
if (relativePath.startsWith(BACKUPS)) {
return this.extUri.joinPath(this.extUri.dirname(this.fileSystemBackupsHome), relativePath);
// Backup Resource
if (this.backupHome && this.fileSystemBackupsHome && this.extUri.isEqualOrParent(userDataResource, this.backupHome)) {
const relativePath = this.extUri.relativePath(this.backupHome, userDataResource);
return relativePath ? this.extUri.joinPath(this.fileSystemBackupsHome, relativePath) : this.fileSystemBackupsHome;
}
const relativePath = this.extUri.relativePath(this.userDataHome, userDataResource)!;
return this.extUri.joinPath(this.fileSystemUserDataHome, relativePath);
}
@@ -151,9 +155,9 @@ export class FileUserDataProvider extends Disposable implements
const relativePath = this.extUri.relativePath(this.fileSystemUserDataHome, fileSystemResource);
return relativePath ? this.extUri.joinPath(this.userDataHome, relativePath) : this.userDataHome;
}
if (this.extUri.isEqualOrParent(fileSystemResource, this.fileSystemBackupsHome)) {
if (this.backupHome && this.fileSystemBackupsHome && this.extUri.isEqualOrParent(fileSystemResource, this.fileSystemBackupsHome)) {
const relativePath = this.extUri.relativePath(this.fileSystemBackupsHome, fileSystemResource);
return relativePath ? this.extUri.joinPath(this.userDataHome, BACKUPS, relativePath) : this.extUri.joinPath(this.userDataHome, BACKUPS);
return relativePath ? this.extUri.joinPath(this.backupHome, relativePath) : this.backupHome;
}
return null;
}

View File

@@ -7,7 +7,6 @@ import * as assert from 'assert';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import * as uuid from 'vs/base/common/uuid';
import * as pfs from 'vs/base/node/pfs';
import { IFileService, FileChangeType, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, FileType, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
@@ -17,29 +16,21 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file
import { joinPath, dirname } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { BACKUPS } from 'vs/platform/environment/common/environment';
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { Emitter, Event } from 'vs/base/common/event';
import { timeout } from 'vs/base/common/async';
class TestBrowserWorkbenchEnvironmentService extends BrowserWorkbenchEnvironmentService {
testUserRoamingDataHome!: URI;
get userRoamingDataHome(): URI {
return this.testUserRoamingDataHome;
}
}
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
suite('FileUserDataProvider', () => {
let testObject: IFileService;
let rootPath: string;
let userDataPath: string;
let backupsPath: string;
let userDataResource: URI;
let rootResource: URI;
let userDataHomeOnDisk: URI;
let backupWorkspaceHomeOnDisk: URI;
let environmentService: IWorkbenchEnvironmentService;
const disposables = new DisposableStore();
let fileUserDataProvider: FileUserDataProvider;
setup(async () => {
const logService = new NullLogService();
@@ -50,237 +41,238 @@ suite('FileUserDataProvider', () => {
disposables.add(diskFileSystemProvider);
disposables.add(testObject.registerProvider(Schemas.file, diskFileSystemProvider));
rootPath = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid());
userDataPath = path.join(rootPath, 'user');
backupsPath = path.join(rootPath, BACKUPS);
userDataResource = URI.file(userDataPath).with({ scheme: Schemas.userData });
await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]);
const workspaceId = 'workspaceId';
environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId, logsPath: URI.file('logFile') });
const environmentService = new TestBrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') });
environmentService.testUserRoamingDataHome = userDataResource;
rootResource = URI.file(path.join(os.tmpdir(), 'vsctests', uuid.generateUuid()));
userDataHomeOnDisk = joinPath(rootResource, 'user');
const backupHome = joinPath(rootResource, 'Backups');
backupWorkspaceHomeOnDisk = joinPath(backupHome, workspaceId);
await Promise.all([testObject.createFolder(userDataHomeOnDisk), testObject.createFolder(backupWorkspaceHomeOnDisk)]);
const userDataFileSystemProvider = new FileUserDataProvider(URI.file(userDataPath), URI.file(backupsPath), diskFileSystemProvider, environmentService, logService);
disposables.add(userDataFileSystemProvider);
disposables.add(testObject.registerProvider(Schemas.userData, userDataFileSystemProvider));
fileUserDataProvider = new FileUserDataProvider(userDataHomeOnDisk, backupWorkspaceHomeOnDisk, diskFileSystemProvider, environmentService, logService);
disposables.add(fileUserDataProvider);
disposables.add(testObject.registerProvider(Schemas.userData, fileUserDataProvider));
});
teardown(async () => {
fileUserDataProvider.dispose(); // need to dispose first, otherwise del will fail (https://github.com/microsoft/vscode/issues/106283)
await testObject.del(rootResource, { recursive: true });
disposables.clear();
await pfs.rimraf(rootPath, pfs.RimRafMode.MOVE);
});
test('exists return false when file does not exist', async () => {
const exists = await testObject.exists(joinPath(userDataResource, 'settings.json'));
const exists = await testObject.exists(environmentService.settingsResource);
assert.equal(exists, false);
});
test('read file throws error if not exist', async () => {
try {
await testObject.readFile(joinPath(userDataResource, 'settings.json'));
await testObject.readFile(environmentService.settingsResource);
assert.fail('Should fail since file does not exist');
} catch (e) { }
});
test('read existing file', async () => {
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}');
const result = await testObject.readFile(joinPath(userDataResource, 'settings.json'));
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('{}'));
const result = await testObject.readFile(environmentService.settingsResource);
assert.equal(result.value, '{}');
});
test('create file', async () => {
const resource = joinPath(userDataResource, 'settings.json');
const resource = environmentService.settingsResource;
const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual2 = await pfs.readFile(path.join(userDataPath, 'settings.json'));
assert.equal(actual2, '{}');
const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json'));
assert.equal(actual2.value.toString(), '{}');
});
test('write file creates the file if not exist', async () => {
const resource = joinPath(userDataResource, 'settings.json');
const resource = environmentService.settingsResource;
const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual2 = await pfs.readFile(path.join(userDataPath, 'settings.json'));
assert.equal(actual2, '{}');
const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json'));
assert.equal(actual2.value.toString(), '{}');
});
test('write to existing file', async () => {
const resource = joinPath(userDataResource, 'settings.json');
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}');
const resource = environmentService.settingsResource;
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('{}'));
const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{a:1}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual2 = await pfs.readFile(path.join(userDataPath, 'settings.json'));
assert.equal(actual2, '{a:1}');
const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json'));
assert.equal(actual2.value.toString(), '{a:1}');
});
test('delete file', async () => {
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '');
await testObject.del(joinPath(userDataResource, 'settings.json'));
const result = await pfs.exists(path.join(userDataPath, 'settings.json'));
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString(''));
await testObject.del(environmentService.settingsResource);
const result = await testObject.exists(joinPath(userDataHomeOnDisk, 'settings.json'));
assert.equal(false, result);
});
test('resolve file', async () => {
await pfs.writeFile(path.join(userDataPath, 'settings.json'), '');
const result = await testObject.resolve(joinPath(userDataResource, 'settings.json'));
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString(''));
const result = await testObject.resolve(environmentService.settingsResource);
assert.ok(!result.isDirectory);
assert.ok(result.children === undefined);
});
test('exists return false for folder that does not exist', async () => {
const exists = await testObject.exists(joinPath(userDataResource, 'snippets'));
const exists = await testObject.exists(environmentService.snippetsHome);
assert.equal(exists, false);
});
test('exists return true for folder that exists', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
const exists = await testObject.exists(joinPath(userDataResource, 'snippets'));
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
const exists = await testObject.exists(environmentService.snippetsHome);
assert.equal(exists, true);
});
test('read file throws error for folder', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
try {
await testObject.readFile(joinPath(userDataResource, 'snippets'));
await testObject.readFile(environmentService.snippetsHome);
assert.fail('Should fail since read file is not supported for folders');
} catch (e) { }
});
test('read file under folder', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
await pfs.writeFile(path.join(userDataPath, 'snippets', 'settings.json'), '{}');
const resource = joinPath(userDataResource, 'snippets/settings.json');
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}'));
const resource = joinPath(environmentService.snippetsHome, 'settings.json');
const actual = await testObject.readFile(resource);
assert.equal(actual.resource.toString(), resource.toString());
assert.equal(actual.value, '{}');
});
test('read file under sub folder', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets', 'java'));
await pfs.writeFile(path.join(userDataPath, 'snippets', 'java', 'settings.json'), '{}');
const resource = joinPath(userDataResource, 'snippets/java/settings.json');
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets', 'java'));
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'java', 'settings.json'), VSBuffer.fromString('{}'));
const resource = joinPath(environmentService.snippetsHome, 'java/settings.json');
const actual = await testObject.readFile(resource);
assert.equal(actual.resource.toString(), resource.toString());
assert.equal(actual.value, '{}');
});
test('create file under folder that exists', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
const resource = joinPath(userDataResource, 'snippets/settings.json');
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
const resource = joinPath(environmentService.snippetsHome, 'settings.json');
const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual2 = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json'));
assert.equal(actual2, '{}');
const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'));
assert.equal(actual2.value.toString(), '{}');
});
test('create file under folder that does not exist', async () => {
const resource = joinPath(userDataResource, 'snippets/settings.json');
const resource = joinPath(environmentService.snippetsHome, 'settings.json');
const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual2 = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json'));
assert.equal(actual2, '{}');
const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'));
assert.equal(actual2.value.toString(), '{}');
});
test('write to not existing file under container that exists', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
const resource = joinPath(userDataResource, 'snippets/settings.json');
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
const resource = joinPath(environmentService.snippetsHome, 'settings.json');
const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json'));
assert.equal(actual, '{}');
const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'));
assert.equal(actual.value.toString(), '{}');
});
test('write to not existing file under container that does not exists', async () => {
const resource = joinPath(userDataResource, 'snippets/settings.json');
const resource = joinPath(environmentService.snippetsHome, 'settings.json');
const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json'));
assert.equal(actual, '{}');
const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'));
assert.equal(actual.value.toString(), '{}');
});
test('write to existing file under container', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
await pfs.writeFile(path.join(userDataPath, 'snippets', 'settings.json'), '{}');
const resource = joinPath(userDataResource, 'snippets/settings.json');
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}'));
const resource = joinPath(environmentService.snippetsHome, 'settings.json');
const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{a:1}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json'));
assert.equal(actual.toString(), '{a:1}');
const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'));
assert.equal(actual.value.toString(), '{a:1}');
});
test('write file under sub container', async () => {
const resource = joinPath(userDataResource, 'snippets/java/settings.json');
const resource = joinPath(environmentService.snippetsHome, 'java/settings.json');
const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}'));
assert.equal(actual1.resource.toString(), resource.toString());
const actual = await pfs.readFile(path.join(userDataPath, 'snippets', 'java', 'settings.json'));
assert.equal(actual, '{}');
const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'java', 'settings.json'));
assert.equal(actual.value.toString(), '{}');
});
test('delete throws error for folder that does not exist', async () => {
try {
await testObject.del(joinPath(userDataResource, 'snippets'));
await testObject.del(environmentService.snippetsHome);
assert.fail('Should fail the folder does not exist');
} catch (e) { }
});
test('delete not existing file under container that exists', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
try {
await testObject.del(joinPath(userDataResource, 'snippets/settings.json'));
await testObject.del(joinPath(environmentService.snippetsHome, 'settings.json'));
assert.fail('Should fail since file does not exist');
} catch (e) { }
});
test('delete not existing file under container that does not exists', async () => {
try {
await testObject.del(joinPath(userDataResource, 'snippets/settings.json'));
await testObject.del(joinPath(environmentService.snippetsHome, 'settings.json'));
assert.fail('Should fail since file does not exist');
} catch (e) { }
});
test('delete existing file under folder', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
await pfs.writeFile(path.join(userDataPath, 'snippets', 'settings.json'), '{}');
await testObject.del(joinPath(userDataResource, 'snippets/settings.json'));
const exists = await pfs.exists(path.join(userDataPath, 'snippets', 'settings.json'));
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}'));
await testObject.del(joinPath(environmentService.snippetsHome, 'settings.json'));
const exists = await testObject.exists(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'));
assert.equal(exists, false);
});
test('resolve folder', async () => {
await pfs.mkdirp(path.join(userDataPath, 'snippets'));
await pfs.writeFile(path.join(userDataPath, 'snippets', 'settings.json'), '{}');
const result = await testObject.resolve(joinPath(userDataResource, 'snippets'));
await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets'));
await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}'));
const result = await testObject.resolve(environmentService.snippetsHome);
assert.ok(result.isDirectory);
assert.ok(result.children !== undefined);
assert.equal(result.children!.length, 1);
assert.equal(result.children![0].resource.toString(), joinPath(userDataResource, 'snippets/settings.json').toString());
assert.equal(result.children![0].resource.toString(), joinPath(environmentService.snippetsHome, 'settings.json').toString());
});
test('read backup file', async () => {
await pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}');
const result = await testObject.readFile(joinPath(userDataResource, `${BACKUPS}/backup.json`));
await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'), VSBuffer.fromString('{}'));
const result = await testObject.readFile(joinPath(environmentService.backupWorkspaceHome!, `backup.json`));
assert.equal(result.value, '{}');
});
test('create backup file', async () => {
await testObject.createFile(joinPath(userDataResource, `${BACKUPS}/backup.json`), VSBuffer.fromString('{}'));
const result = await pfs.readFile(path.join(backupsPath, 'backup.json'));
assert.equal(result, '{}');
await testObject.createFile(joinPath(environmentService.backupWorkspaceHome!, `backup.json`), VSBuffer.fromString('{}'));
const result = await testObject.readFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'));
assert.equal(result.value.toString(), '{}');
});
test('write backup file', async () => {
await pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}');
await testObject.writeFile(joinPath(userDataResource, `${BACKUPS}/backup.json`), VSBuffer.fromString('{a:1}'));
const result = await pfs.readFile(path.join(backupsPath, 'backup.json'));
assert.equal(result, '{a:1}');
await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'), VSBuffer.fromString('{}'));
await testObject.writeFile(joinPath(environmentService.backupWorkspaceHome!, `backup.json`), VSBuffer.fromString('{a:1}'));
const result = await testObject.readFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'));
assert.equal(result.value.toString(), '{a:1}');
});
test('resolve backups folder', async () => {
await pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}');
const result = await testObject.resolve(joinPath(userDataResource, BACKUPS));
await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'), VSBuffer.fromString('{}'));
const result = await testObject.resolve(environmentService.backupWorkspaceHome!);
assert.ok(result.isDirectory);
assert.ok(result.children !== undefined);
assert.equal(result.children!.length, 1);
assert.equal(result.children![0].resource.toString(), joinPath(userDataResource, `${BACKUPS}/backup.json`).toString());
assert.equal(result.children![0].resource.toString(), joinPath(environmentService.backupWorkspaceHome!, `backup.json`).toString());
});
});
@@ -315,7 +307,7 @@ suite('FileUserDataProvider - Watching', () => {
let testObject: IFileService;
let localBackupsResource: URI;
let localUserDataResource: URI;
let userDataResource: URI;
let environmentService: IWorkbenchEnvironmentService;
const disposables = new DisposableStore();
const fileEventEmitter: Emitter<readonly IFileChange[]> = new Emitter<readonly IFileChange[]>();
@@ -323,15 +315,11 @@ suite('FileUserDataProvider - Watching', () => {
setup(() => {
const rootPath = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid());
const userDataPath = path.join(rootPath, 'user');
const backupsPath = path.join(rootPath, BACKUPS);
localBackupsResource = URI.file(backupsPath);
localUserDataResource = URI.file(userDataPath);
userDataResource = localUserDataResource.with({ scheme: Schemas.userData });
environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') });
const environmentService = new TestBrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') });
environmentService.testUserRoamingDataHome = userDataResource;
const rootResource = URI.file(path.join(os.tmpdir(), 'vsctests', uuid.generateUuid()));
localUserDataResource = joinPath(rootResource, 'user');
localBackupsResource = joinPath(rootResource, 'Backups');
const userDataFileSystemProvider = new FileUserDataProvider(localUserDataResource, localBackupsResource, new TestFileSystemProvider(fileEventEmitter.event), environmentService, new NullLogService());
disposables.add(userDataFileSystemProvider);
@@ -341,12 +329,10 @@ suite('FileUserDataProvider - Watching', () => {
disposables.add(testObject.registerProvider(Schemas.userData, userDataFileSystemProvider));
});
teardown(() => {
disposables.clear();
});
teardown(() => disposables.clear());
test('file added change event', done => {
const expected = joinPath(userDataResource, 'settings.json');
const expected = environmentService.settingsResource;
const target = joinPath(localUserDataResource, 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.ADDED)) {
@@ -360,7 +346,7 @@ suite('FileUserDataProvider - Watching', () => {
});
test('file updated change event', done => {
const expected = joinPath(userDataResource, 'settings.json');
const expected = environmentService.settingsResource;
const target = joinPath(localUserDataResource, 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.UPDATED)) {
@@ -374,7 +360,7 @@ suite('FileUserDataProvider - Watching', () => {
});
test('file deleted change event', done => {
const expected = joinPath(userDataResource, 'settings.json');
const expected = environmentService.settingsResource;
const target = joinPath(localUserDataResource, 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.DELETED)) {
@@ -388,7 +374,7 @@ suite('FileUserDataProvider - Watching', () => {
});
test('file under folder created change event', done => {
const expected = joinPath(userDataResource, 'snippets', 'settings.json');
const expected = joinPath(environmentService.snippetsHome, 'settings.json');
const target = joinPath(localUserDataResource, 'snippets', 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.ADDED)) {
@@ -402,7 +388,7 @@ suite('FileUserDataProvider - Watching', () => {
});
test('file under folder updated change event', done => {
const expected = joinPath(userDataResource, 'snippets', 'settings.json');
const expected = joinPath(environmentService.snippetsHome, 'settings.json');
const target = joinPath(localUserDataResource, 'snippets', 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.UPDATED)) {
@@ -416,7 +402,7 @@ suite('FileUserDataProvider - Watching', () => {
});
test('file under folder deleted change event', done => {
const expected = joinPath(userDataResource, 'snippets', 'settings.json');
const expected = joinPath(environmentService.snippetsHome, 'settings.json');
const target = joinPath(localUserDataResource, 'snippets', 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.DELETED)) {
@@ -444,7 +430,7 @@ suite('FileUserDataProvider - Watching', () => {
});
test('backup file created change event', done => {
const expected = joinPath(userDataResource, BACKUPS, 'settings.json');
const expected = joinPath(environmentService.backupWorkspaceHome!, 'settings.json');
const target = joinPath(localBackupsResource, 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.ADDED)) {
@@ -458,7 +444,7 @@ suite('FileUserDataProvider - Watching', () => {
});
test('backup file update change event', done => {
const expected = joinPath(userDataResource, BACKUPS, 'settings.json');
const expected = joinPath(environmentService.backupWorkspaceHome!, 'settings.json');
const target = joinPath(localBackupsResource, 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.UPDATED)) {
@@ -472,7 +458,7 @@ suite('FileUserDataProvider - Watching', () => {
});
test('backup file delete change event', done => {
const expected = joinPath(userDataResource, BACKUPS, 'settings.json');
const expected = joinPath(environmentService.backupWorkspaceHome!, 'settings.json');
const target = joinPath(localBackupsResource, 'settings.json');
testObject.onDidFilesChange(e => {
if (e.contains(expected, FileChangeType.DELETED)) {

View File

@@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { firstIndex, move } from 'vs/base/common/arrays';
import { move } from 'vs/base/common/arrays';
import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
import { isEqual } from 'vs/base/common/resources';
@@ -442,8 +442,8 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode
}
move(from: string, to: string): void {
const fromIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === from);
const toIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === to);
const fromIndex = this.viewDescriptorItems.findIndex(v => v.viewDescriptor.id === from);
const toIndex = this.viewDescriptorItems.findIndex(v => v.viewDescriptor.id === to);
const fromViewDescriptor = this.viewDescriptorItems[fromIndex];
const toViewDescriptor = this.viewDescriptorItems[toIndex];
@@ -531,7 +531,7 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode
this.contextKeys.delete(key);
}
}
const index = firstIndex(this.viewDescriptorItems, i => i.viewDescriptor.id === viewDescriptor.id);
const index = this.viewDescriptorItems.findIndex(i => i.viewDescriptor.id === viewDescriptor.id);
if (index !== -1) {
removed.push(viewDescriptor);
const viewDescriptorItem = this.viewDescriptorItems[index];

View File

@@ -1,467 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 * as sinon from 'sinon';
import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewContainerModel, IViewDescriptorService, ViewContainer } from 'vs/workbench/common/views';
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { move } from 'vs/base/common/arrays';
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
const ViewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
const ViewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
class ViewDescriptorSequence {
readonly elements: IViewDescriptor[];
private disposables: IDisposable[] = [];
constructor(model: IViewContainerModel) {
this.elements = [...model.visibleViewDescriptors];
model.onDidAddVisibleViewDescriptors(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables);
model.onDidRemoveVisibleViewDescriptors(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables);
model.onDidMoveVisibleViewDescriptors(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables);
}
dispose() {
this.disposables = dispose(this.disposables);
}
}
suite('ViewContainerModel', () => {
let container: ViewContainer;
let disposableStore: DisposableStore;
let contextKeyService: IContextKeyService;
let viewDescriptorService: IViewDescriptorService;
let storageService: IStorageService;
setup(() => {
disposableStore = new DisposableStore();
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
contextKeyService = instantiationService.createInstance(ContextKeyService);
instantiationService.stub(IContextKeyService, contextKeyService);
storageService = instantiationService.get(IStorageService);
viewDescriptorService = instantiationService.createInstance(ViewDescriptorService);
});
teardown(() => {
disposableStore.dispose();
ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container);
ViewContainerRegistry.deregisterViewContainer(container);
});
test('empty model', function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
assert.equal(testObject.visibleViewDescriptors.length, 0);
});
test('register/unregister', () => {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1'
};
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 1);
assert.equal(target.elements.length, 1);
assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor);
assert.deepEqual(target.elements[0], viewDescriptor);
ViewsRegistry.deregisterViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
});
test('when contexts', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true)
};
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in');
assert.equal(target.elements.length, 0);
const key = contextKeyService.createKey('showview1', false);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true');
assert.equal(target.elements.length, 0);
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear');
assert.equal(target.elements.length, 1);
assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor);
assert.equal(target.elements[0], viewDescriptor);
key.set(false);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear');
assert.equal(target.elements.length, 0);
ViewsRegistry.deregisterViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore');
assert.equal(target.elements.length, 0);
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore');
assert.equal(target.elements.length, 0);
});
test('when contexts - multiple', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' };
const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) };
ViewsRegistry.registerViews([view1, view2], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'only view1 should be visible');
assert.deepEqual(target.elements, [view1], 'only view1 should be visible');
const key = contextKeyService.createKey('showview2', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'still only view1 should be visible');
assert.deepEqual(target.elements, [view1], 'still only view1 should be visible');
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible');
assert.deepEqual(target.elements, [view1, view2], 'both views should be visible');
ViewsRegistry.deregisterViews([view1, view2], container);
});
test('when contexts - multiple 2', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) };
const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' };
ViewsRegistry.registerViews([view1, view2], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'only view2 should be visible');
assert.deepEqual(target.elements, [view2], 'only view2 should be visible');
const key = contextKeyService.createKey('showview1', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'still only view2 should be visible');
assert.deepEqual(target.elements, [view2], 'still only view2 should be visible');
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible');
assert.deepEqual(target.elements, [view1, view2], 'both views should be visible');
ViewsRegistry.deregisterViews([view1, view2], container);
});
test('setVisible', () => {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true };
const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', canToggleVisibility: true };
const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true };
ViewsRegistry.registerViews([view1, view2, view3], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3]);
assert.deepEqual(target.elements, [view1, view2, view3]);
testObject.setVisible('view2', true);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen');
assert.deepEqual(target.elements, [view1, view2, view3]);
testObject.setVisible('view2', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view2 should hide');
assert.deepEqual(target.elements, [view1, view3]);
testObject.setVisible('view1', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view3], 'view1 should hide');
assert.deepEqual(target.elements, [view3]);
testObject.setVisible('view3', false);
assert.deepEqual(testObject.visibleViewDescriptors, [], 'view3 shoud hide');
assert.deepEqual(target.elements, []);
testObject.setVisible('view1', true);
assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'view1 should show');
assert.deepEqual(target.elements, [view1]);
testObject.setVisible('view3', true);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view3 should show');
assert.deepEqual(target.elements, [view1, view3]);
testObject.setVisible('view2', true);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should show');
assert.deepEqual(target.elements, [view1, view2, view3]);
ViewsRegistry.deregisterViews([view1, view2, view3], container);
assert.deepEqual(testObject.visibleViewDescriptors, []);
assert.deepEqual(target.elements, []);
});
test('move', () => {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' };
const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' };
const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' };
ViewsRegistry.registerViews([view1, view2, view3], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK');
assert.deepEqual(target.elements, [view1, view2, view3], 'sql views should be OK');
testObject.move('view3', 'view1');
assert.deepEqual(testObject.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front');
assert.deepEqual(target.elements, [view3, view1, view2]);
testObject.move('view1', 'view2');
assert.deepEqual(testObject.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end');
assert.deepEqual(target.elements, [view3, view2, view1]);
testObject.move('view1', 'view3');
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front');
assert.deepEqual(target.elements, [view1, view3, view2]);
testObject.move('view2', 'view3');
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle');
assert.deepEqual(target.elements, [view1, view2, view3]);
});
test('view states', async function () {
storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL);
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1'
};
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state');
assert.equal(target.elements.length, 0);
});
test('view states and when contexts', async function () {
storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL);
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true)
};
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in');
assert.equal(target.elements.length, 0);
const key = contextKeyService.createKey('showview1', false);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true');
assert.equal(target.elements.length, 0);
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state');
assert.equal(target.elements.length, 0);
});
test('view states and when contexts multiple views', async function () {
storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL);
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const view1: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview', true)
};
const view2: IViewDescriptor = {
id: 'view2',
ctorDescriptor: null!,
name: 'Test View 2',
};
const view3: IViewDescriptor = {
id: 'view3',
ctorDescriptor: null!,
name: 'Test View 3',
when: ContextKeyExpr.equals('showview', true)
};
ViewsRegistry.registerViews([view1, view2, view3], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible');
assert.deepEqual(target.elements, [view2]);
const key = contextKeyService.createKey('showview', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible');
assert.deepEqual(target.elements, [view2]);
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.deepEqual(testObject.visibleViewDescriptors, [view2, view3], 'view3 should be visible');
assert.deepEqual(target.elements, [view2, view3]);
key.set(false);
await new Promise(c => setTimeout(c, 30));
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible');
assert.deepEqual(target.elements, [view2]);
});
test('remove event is not triggered if view was hidden and removed', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true),
canToggleVisibility: true
};
ViewsRegistry.registerViews([viewDescriptor], container);
const key = contextKeyService.createKey('showview1', true);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear after context is set');
assert.equal(target.elements.length, 1);
testObject.setVisible('view1', false);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false');
assert.equal(target.elements.length, 0);
const targetEvent = sinon.spy(testObject.onDidRemoveVisibleViewDescriptors);
key.set(false);
await new Promise(c => setTimeout(c, 30));
assert.ok(!targetEvent.called, 'remove event should not be called since it is already hidden');
});
test('add event is not triggered if view was set visible (when visible) and not active', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true),
canToggleVisibility: true
};
const key = contextKeyService.createKey('showview1', true);
key.set(false);
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors);
testObject.setVisible('view1', true);
assert.ok(!targetEvent.called, 'add event should not be called since it is already visible');
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
});
test('remove event is not triggered if view was hidden and not active', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true),
canToggleVisibility: true
};
const key = contextKeyService.createKey('showview1', true);
key.set(false);
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors);
testObject.setVisible('view1', false);
assert.ok(!targetEvent.called, 'add event should not be called since it is disabled');
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
});
test('add event is not triggered if view was set visible (when not visible) and not active', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true),
canToggleVisibility: true
};
const key = contextKeyService.createKey('showview1', true);
key.set(false);
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
testObject.setVisible('view1', false);
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors);
testObject.setVisible('view1', true);
assert.ok(!targetEvent.called, 'add event should not be called since it is disabled');
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
});
});

View File

@@ -307,7 +307,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi
);
}
abstract async enterWorkspace(path: URI): Promise<void>;
abstract enterWorkspace(path: URI): Promise<void>;
protected async doEnterWorkspace(path: URI): Promise<IEnterWorkspaceResult | null> {
if (!!this.environmentService.extensionTestsLocationURI) {

View File

@@ -13,12 +13,12 @@ import { WorkspaceService } from 'vs/workbench/services/configuration/browser/co
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { basename } from 'vs/base/common/resources';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -31,7 +31,6 @@ import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron
import { isMacintosh } from 'vs/base/common/platform';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingService {
@@ -173,7 +172,6 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi
// Reinitialize backup service
this.environmentService.configuration.backupPath = result.backupPath;
this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined;
if (this.backupFileService instanceof BackupFileService) {
this.backupFileService.reinitialize();
}