mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 18:46:36 -05:00
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:
@@ -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()) {
|
||||
@@ -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.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))!);
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
@@ -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[]>;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
})();
|
||||
`
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface IHostService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
|
||||
//#region Focus
|
||||
|
||||
/**
|
||||
@@ -64,7 +65,6 @@ export interface IHostService {
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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);
|
||||
@@ -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')) }
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -77,8 +77,6 @@ export interface IWorkbenchThemeService extends IThemeService {
|
||||
getProductIconTheme(): IWorkbenchProductIconTheme;
|
||||
getProductIconThemes(): Promise<IWorkbenchProductIconTheme[]>;
|
||||
onDidProductIconThemeChange: Event<IWorkbenchProductIconTheme>;
|
||||
|
||||
setOSHighContrast(highContrast: boolean): void;
|
||||
}
|
||||
|
||||
export interface IColorCustomizations {
|
||||
|
||||
@@ -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);
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user