diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index e442d7ad0ef0d61ef840038d97c1d50eacdc76ab..422ec4493a65d7d2aa28dd0ff492a1f3594baf9d 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -90,6 +90,8 @@ export interface IExtensionHostDebugParams extends IDebugParams { debugId?: string; } +export const BACKUPS = 'Backups'; + export interface IEnvironmentService { _serviceBrand: any; @@ -105,9 +107,13 @@ export interface IEnvironmentService { appNameLong: string; appQuality?: string; appSettingsHome: URI; + + // user roaming data + userRoamingDataHome: URI; settingsResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; + localeResource: URI; machineSettingsHome: URI; machineSettingsResource: URI; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 55c3d8302ae8cc90749ee9133661937c8b4eb429..744c9b936fe7888d4ce2499d8b4f27b100b59b54 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEnvironmentService, ParsedArgs, IDebugParams, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, ParsedArgs, IDebugParams, IExtensionHostDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; import * as crypto from 'crypto'; import * as paths from 'vs/base/node/paths'; import * as os from 'os'; @@ -115,7 +115,10 @@ export class EnvironmentService implements IEnvironmentService { get appSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'User')); } @memoize - get settingsResource(): URI { return resources.joinPath(this.appSettingsHome, 'settings.json'); } + get userRoamingDataHome(): URI { return this.appSettingsHome; } + + @memoize + get settingsResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'settings.json'); } @memoize get machineSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'Machine')); } @@ -136,16 +139,19 @@ export class EnvironmentService implements IEnvironmentService { get settingsSearchUrl(): string | undefined { return product.settingsSearchUrl; } @memoize - get keybindingsResource(): URI { return resources.joinPath(this.appSettingsHome, 'keybindings.json'); } + get keybindingsResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'keybindings.json'); } + + @memoize + get keyboardLayoutResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); } @memoize - get keyboardLayoutResource(): URI { return resources.joinPath(this.appSettingsHome, 'keyboardLayout.json'); } + get localeResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'locale.json'); } @memoize get isExtensionDevelopment(): boolean { return !!this._args.extensionDevelopmentPath; } @memoize - get backupHome(): string { return path.join(this.userDataPath, 'Backups'); } + get backupHome(): string { return path.join(this.userDataPath, BACKUPS); } @memoize get backupWorkspacesPath(): string { return path.join(this.backupHome, 'workspaces.json'); } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 9519bdb5158284b2580d76162ba80c78360e31da..35d76e44b477950a262e0b02d9034a4567dd6877 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -19,7 +19,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, IFileSystemProvider } from 'vs/platform/files/common/files'; import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -35,10 +35,7 @@ import { hash } from 'vs/base/common/hash'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import { ProductService } from 'vs/platform/product/browser/productService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; import { joinPath, dirname } from 'vs/base/common/resources'; -import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; -import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; class CodeRendererMain extends Disposable { @@ -87,8 +84,7 @@ class CodeRendererMain extends Disposable { serviceCollection.set(ILogService, logService); // Environment - const remoteUserDataUri = this.getRemoteUserDataUri(); - const environmentService = new BrowserWorkbenchEnvironmentService(this.configuration, remoteUserDataUri); + const environmentService = new BrowserWorkbenchEnvironmentService(this.configuration); serviceCollection.set(IWorkbenchEnvironmentService, environmentService); // Product @@ -111,16 +107,26 @@ class CodeRendererMain extends Disposable { const fileService = this._register(new FileService(logService)); serviceCollection.set(IFileService, fileService); + let userDataProvider: IFileSystemProvider | undefined = this.configuration.userDataProvider; const connection = remoteAgentService.getConnection(); if (connection) { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); + + if (!userDataProvider) { + const remoteUserDataUri = this.getRemoteUserDataUri(); + if (remoteUserDataUri) { + userDataProvider = this._register(new FileUserDataProvider(remoteUserDataUri, dirname(remoteUserDataUri), remoteFileSystemProvider)); + } + } } // User Data Provider - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(dirname(environmentService.settingsResource), this.getUserDataPovider(fileService, remoteUserDataUri))); + if (userDataProvider) { + fileService.registerProvider(Schemas.userData, userDataProvider); + } const payload = await this.resolveWorkspaceInitializationPayload(); @@ -170,15 +176,6 @@ class CodeRendererMain extends Disposable { return { id: 'empty-window' }; } - private getUserDataPovider(fileService: IFileService, remoteUserDataUri: URI | null): IUserDataProvider { - if (this.configuration.userDataProvider) { - return this.configuration.userDataProvider; - } else if (this.configuration.remoteAuthority && remoteUserDataUri) { - return this._register(new FileUserDataProvider(remoteUserDataUri, fileService)); - } - return this._register(new InMemoryUserDataProvider()); - } - private getRemoteUserDataUri(): URI | null { const element = document.getElementById('vscode-remote-user-data-uri'); if (element) { diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 4ae502390f8e9892a11c42dfbed2df24a05c61a8..14d37470b814c7774a65b4ce689b2613eed44be4 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -28,7 +28,6 @@ import { minimumTranslatedStrings } from 'vs/workbench/contrib/localizations/bro import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { joinPath } from 'vs/base/common/resources'; // Register action to configure locale and related settings const registry = Registry.as(Extensions.WorkbenchActions); @@ -81,8 +80,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo [{ label: updateAndRestart ? localize('yes', "Yes") : localize('restart now', "Restart Now"), run: () => { - const file = joinPath(this.environmentService.appSettingsHome, 'locale.json'); - const updatePromise = updateAndRestart ? this.jsonEditingService.write(file, { key: 'locale', value: locale }, true) : Promise.resolve(undefined); + const updatePromise = updateAndRestart ? this.jsonEditingService.write(this.environmentService.localeResource, { key: 'locale', value: locale }, true) : Promise.resolve(undefined); updatePromise.then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e)); } }, { diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index 14487ed363be1c16475af0e3806c479ec5455abb..b09ecb92ba9b02ac85075f3c8e66991dff4f26e7 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -16,7 +16,6 @@ import { firstIndex } from 'vs/base/common/arrays'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { joinPath } from 'vs/base/common/resources'; export class ConfigureLocaleAction extends Action { public static readonly ID = 'workbench.action.configureLocale'; @@ -66,8 +65,7 @@ export class ConfigureLocaleAction extends Action { } if (selectedLanguage) { - const file = joinPath(this.environmentService.appSettingsHome, 'locale.json'); - await this.jsonEditingService.write(file, { key: 'locale', value: selectedLanguage.label }, true); + await this.jsonEditingService.write(this.environmentService.localeResource, { key: 'locale', value: selectedLanguage.label }, true); const restart = await this.dialogService.confirm({ type: 'info', message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index fe901ee2d710c9a9d7bebd9fe41665b4fdcd9145..e2073066f377cf1df794cf83db7d6c8ec03023c0 100644 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { basename, extname } from 'vs/base/common/path'; +import { extname } from 'vs/base/common/path'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; @@ -19,7 +19,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IFileService } from 'vs/platform/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { isValidBasename } from 'vs/base/common/extpath'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, basename } from 'vs/base/common/resources'; const id = 'workbench.action.openSnippets'; @@ -69,7 +69,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir } existing.push({ - label: basename(file.location.fsPath), + label: basename(file.location), filepath: file.location, description: names.size === 0 ? nls.localize('global.scope', "(global)") @@ -78,9 +78,9 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir } else { // language snippet - const mode = basename(file.location.fsPath).replace(/\.json$/, ''); + const mode = basename(file.location).replace(/\.json$/, ''); existing.push({ - label: basename(file.location.fsPath), + label: basename(file.location), description: `(${modeService.getLanguageName(mode)})`, filepath: file.location }); @@ -88,7 +88,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir } } - const dir = joinPath(envService.appSettingsHome, 'snippets'); + const dir = joinPath(envService.userRoamingDataHome, 'snippets'); for (const mode of modeService.getRegisteredModes()) { const label = modeService.getLanguageName(mode); if (label && !seen.has(mode)) { @@ -220,7 +220,7 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise => { const globalSnippetPicks: SnippetPick[] = [{ scope: nls.localize('new.global_scope', 'global'), label: nls.localize('new.global', "New Global Snippets file..."), - uri: joinPath(envService.appSettingsHome, 'snippets') + uri: joinPath(envService.userRoamingDataHome, 'snippets') }]; const workspaceSnippetPicks: SnippetPick[] = []; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 32e0cbbe86246d1775b83602d9b002a9cf7aa84e..11cbeea9c87b4b2d447341c5349855efa6f5cb09 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -287,7 +287,7 @@ class SnippetsService implements ISnippetsService { } private _initUserSnippets(): Promise { - const userSnippetsFolder = resources.joinPath(this._environmentService.appSettingsHome, 'snippets'); + const userSnippetsFolder = resources.joinPath(this._environmentService.userRoamingDataHome, 'snippets'); return this._fileService.createFolder(userSnippetsFolder).then(() => this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables)); } diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 014ac74121da5a2be7961fa13bbb2231502ce858..c7542c63abe1e374527a7e6abeef6f01313179be 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -51,8 +51,6 @@ import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; -import { dirname } from 'vs/base/common/resources'; class CodeRendererMain extends Disposable { @@ -201,6 +199,9 @@ class CodeRendererMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // User Data Provider + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, URI.file(environmentService.backupHome), diskFileSystemProvider)); + const connection = remoteAgentService.getConnection(); if (connection) { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); @@ -208,8 +209,6 @@ class CodeRendererMain extends Disposable { fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } - // User Data Provider - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(dirname(environmentService.settingsResource), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); const payload = await this.resolveWorkspaceInitializationPayload(environmentService); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 6c125ff67f6e328640675148f020f5cc09e4e950..515c6df2fe3c38172fcf7dade310cd71cfca65a6 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -40,7 +40,6 @@ export class UserConfiguration extends Disposable { this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); - this._register(this.fileService.watch(this.userSettingsResource)); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this.reloadConfigurationScheduler.schedule())); } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 6f187eaaa36c3ad49688231357b85ee514bf373f..be0cfec34208d89d2eaa86c0e8233b1684917397 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -10,7 +10,7 @@ import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import * as json from 'vs/base/common/json'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { parseArgs } from 'vs/platform/environment/node/argv'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; @@ -38,20 +38,20 @@ import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; -import { dirname } from 'vs/base/common/resources'; import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; +import { dirname } from 'vs/base/common/resources'; -class SettingsTestEnvironmentService extends WorkbenchEnvironmentService { +class TestBackupEnvironmentService extends WorkbenchEnvironmentService { - constructor(args: ParsedArgs, _execPath: string, private _settingsPath: string) { - super(args, _execPath); + constructor(private _appSettingsHome: URI) { + super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); } - get appSettingsHome(): URI { return dirname(URI.file(this._settingsPath)); } + get appSettingsHome() { return this._appSettingsHome; } + } suite('ConfigurationEditingService', () => { @@ -105,14 +105,15 @@ suite('ConfigurationEditingService', () => { clearServices(); instantiationService = workbenchInstantiationService(); - const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); + const environmentService = new TestBackupEnvironmentService(URI.file(workspaceDir)); instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(URI.file(workspaceDir), dirname(URI.file(workspaceDir)), diskFileSystemProvider)); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 4554c25471eb8f040e4dce7c5b5385c7f7245e46..308b976db112b4b4247fa55635cd8e2c0dae2b0c 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -10,7 +10,7 @@ import * as path from 'vs/base/common/path'; import * as os from 'os'; import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { parseArgs } from 'vs/platform/environment/node/argv'; import * as pfs from 'vs/base/node/pfs'; import * as uuid from 'vs/base/common/uuid'; @@ -47,16 +47,16 @@ import { SignService } from 'vs/platform/sign/browser/signService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -class SettingsTestEnvironmentService extends WorkbenchEnvironmentService { +class TestEnvironmentService extends WorkbenchEnvironmentService { - constructor(args: ParsedArgs, _execPath: string, private _settingsPath: string) { - super(args, _execPath); + constructor(private _appSettingsHome: URI) { + super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); } - get appSettingsHome(): URI { return dirname(URI.file(this._settingsPath)); } + get appSettingsHome() { return this._appSettingsHome; } + } function setUpFolderWorkspace(folderName: string): Promise<{ parentDir: string, folderDir: string }> { @@ -105,10 +105,9 @@ suite('WorkspaceContextService - Folder', () => { .then(({ parentDir, folderDir }) => { parentResource = parentDir; workspaceResource = folderDir; - const globalSettingsFile = path.join(parentDir, 'settings.json'); - const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), new DiskFileSystemProvider(new NullLogService()))); workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService({}, environmentService, new RemoteAuthorityResolverService(), new SignService())); return (workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir))); }); @@ -168,12 +167,13 @@ suite('WorkspaceContextService - Workspace', () => { parentResource = parentDir; instantiationService = workbenchInstantiationService(); - const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json')); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -227,12 +227,13 @@ suite('WorkspaceContextService - Workspace Editing', () => { parentResource = parentDir; instantiationService = workbenchInstantiationService(); - const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json')); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -487,12 +488,13 @@ suite('WorkspaceService - Initialization', () => { globalSettingsFile = path.join(parentDir, 'settings.json'); const instantiationService = workbenchInstantiationService(); - const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -750,12 +752,13 @@ suite('WorkspaceConfigurationService - Folder', () => { globalSettingsFile = path.join(parentDir, 'settings.json'); const instantiationService = workbenchInstantiationService(); - const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -1040,7 +1043,7 @@ suite('WorkspaceConfigurationService - Folder', () => { suite('WorkspaceConfigurationService-Multiroot', () => { - let parentResource: string, workspaceContextService: IWorkspaceContextService, environmentService: IWorkbenchEnvironmentService, jsonEditingServce: IJSONEditingService, testObject: IConfigurationService, globalSettingsFile: string; + let parentResource: string, workspaceContextService: IWorkspaceContextService, jsonEditingServce: IJSONEditingService, testObject: IConfigurationService, globalSettingsFile: string; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); suiteSetup(() => { @@ -1079,12 +1082,13 @@ suite('WorkspaceConfigurationService-Multiroot', () => { globalSettingsFile = path.join(parentDir, 'settings.json'); const instantiationService = workbenchInstantiationService(); - environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -1481,12 +1485,12 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { remoteSettingsFile = path.join(parentDir, 'remote-settings.json'); instantiationService = workbenchInstantiationService(); - const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); + const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteEnvironmentPromise = new Promise>(c => resolveRemoteEnvironment = () => c({ settingsPath: URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }) })); const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(environmentService.appSettingsHome.with({ scheme: Schemas.userData }), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() }; testObject = new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, testObject); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 97213c6b7dc7cb9a403d800c76aa82e97f0bf4ce..f6945be12a37ecccd51dfad9725703508e3e4e63 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -63,24 +63,17 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration(); - constructor(configuration: IWorkbenchConstructionOptions, remoteUserDataUri: URI | null) { + constructor(configuration: IWorkbenchConstructionOptions) { this.args = { _: [] }; this.appRoot = '/web/'; this.appNameLong = 'Visual Studio Code - Web'; this.configuration.remoteAuthority = configuration.remoteAuthority; - - if (remoteUserDataUri) { - this.appSettingsHome = remoteUserDataUri; - this.settingsResource = joinPath(this.appSettingsHome, 'settings.json').with({ scheme: Schemas.userData }); - this.keybindingsResource = joinPath(this.appSettingsHome, 'keybindings.json').with({ scheme: Schemas.userData }); - } else { - const appSettingsHome = URI.file('/User').with({ scheme: Schemas.userData }); - this.settingsResource = joinPath(appSettingsHome, 'settings.json'); - this.keybindingsResource = joinPath(appSettingsHome, 'keybindings.json'); - } - - this.keyboardLayoutResource = joinPath(this.appSettingsHome, 'keyboardLayout.json'); + this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData }); + this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json'); + this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json'); + this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); + this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json'); this.logsPath = '/web/logs'; @@ -104,9 +97,11 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { appNameLong: string; appQuality?: string; appSettingsHome: URI; + userRoamingDataHome: URI; settingsResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; + localeResource: URI; machineSettingsHome: URI; machineSettingsResource: URI; settingsSearchBuildId?: number; diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/node/environmentService.ts index bb72c8785b8c56e4b95945320700457b34648bff..48be78c71f7ae4e7cfbeb0f2cd84f732f0031811 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/node/environmentService.ts @@ -8,7 +8,6 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { @@ -27,8 +26,5 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I } @memoize - get settingsResource(): URI { return joinPath(this.appSettingsHome, 'settings.json').with({ scheme: Schemas.userData }); } - - @memoize - get keybindingsResource(): URI { return joinPath(this.appSettingsHome, 'keybindings.json').with({ scheme: Schemas.userData }); } + get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 25570e1556cfbbdb8e0b19ebce821ff153b0d1d3..1a165e03627ea03d2df2be339ff931d68d5c3af1 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -579,7 +579,6 @@ class UserKeybindings extends Disposable { this._onDidChange.fire(); } }), 50)); - this._register(this.fileService.watch(this.keybindingsResource)); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.keybindingsResource))(() => this.reloadConfigurationScheduler.schedule())); } diff --git a/src/vs/workbench/services/keybinding/browser/keymapService.ts b/src/vs/workbench/services/keybinding/browser/keymapService.ts index 50424158297217ee50f49ce924c08cfe5fbf2df2..401dede8db9d47e52283f9ccc44f2785d01c418b 100644 --- a/src/vs/workbench/services/keybinding/browser/keymapService.ts +++ b/src/vs/workbench/services/keybinding/browser/keymapService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, toDisposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IKeymapService, IKeyboardLayoutInfo, IKeyboardMapping, IWindowsKeyboardMapping, KeymapInfo, IRawMixedKeyboardMapping, getKeyboardLayoutId, IKeymapInfo } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; @@ -17,9 +17,8 @@ import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { URI } from 'vs/base/common/uri'; -import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { dirname, isEqual } from 'vs/base/common/resources'; import { parse } from 'vs/base/common/json'; import * as objects from 'vs/base/common/objects'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -440,13 +439,11 @@ export class BrowserKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBa } class UserKeyboardLayout extends Disposable { + private readonly reloadConfigurationScheduler: RunOnceScheduler; protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - private fileWatcherDisposable: IDisposable = Disposable.None; - private directoryWatcherDisposable: IDisposable = Disposable.None; - private _keyboardLayout: KeymapInfo | null; get keyboardLayout(): KeymapInfo | null { return this._keyboardLayout; } @@ -458,22 +455,16 @@ class UserKeyboardLayout extends Disposable { this._keyboardLayout = null; - this._register(fileService.onFileChanges(e => this.handleFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(changed => { if (changed) { this._onDidChange.fire(); } }), 50)); - this._register(toDisposable(() => { - this.stopWatchingResource(); - this.stopWatchingDirectory(); - })); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.keyboardLayoutResource))(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { - const exists = await this.fileService.exists(this.keyboardLayoutResource); - this.onResourceExists(exists); await this.reload(); } @@ -492,57 +483,6 @@ class UserKeyboardLayout extends Disposable { return existing ? !objects.equals(existing, this._keyboardLayout) : true; } - private watchResource(): void { - this.fileWatcherDisposable = this.fileService.watch(this.keyboardLayoutResource); - } - - private watchDirectory(): void { - const directory = dirname(this.keyboardLayoutResource); - this.directoryWatcherDisposable = this.fileService.watch(directory); - } - - private stopWatchingResource(): void { - this.fileWatcherDisposable.dispose(); - this.fileWatcherDisposable = Disposable.None; - } - - private stopWatchingDirectory(): void { - this.directoryWatcherDisposable.dispose(); - this.directoryWatcherDisposable = Disposable.None; - } - - private async handleFileEvents(event: FileChangesEvent): Promise { - const events = event.changes; - - let affectedByChanges = false; - - // Find changes that affect the resource - for (const event of events) { - affectedByChanges = isEqual(this.keyboardLayoutResource, event.resource); - if (affectedByChanges) { - if (event.type === FileChangeType.ADDED) { - this.onResourceExists(true); - } else if (event.type === FileChangeType.DELETED) { - this.onResourceExists(false); - } - break; - } - } - - if (affectedByChanges) { - this.reloadConfigurationScheduler.schedule(); - } - } - - private onResourceExists(exists: boolean): void { - if (exists) { - this.stopWatchingDirectory(); - this.watchResource(); - } else { - this.stopWatchingResource(); - this.watchDirectory(); - } - } } class BrowserKeymapService extends Disposable implements IKeymapService { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index d3fecf7080d77034ffa1d71b4b8b557e4b831c00..dc8b05ca6f8885346ae8ba057682c2bbe779381f 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -21,7 +21,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/resour import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; @@ -45,13 +45,22 @@ import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { URI } from 'vs/base/common/uri'; -import { UserDataFileSystemProvider } from 'vs/workbench/services/userData/common/userDataFileSystemProvider'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; +import { parseArgs } from 'vs/platform/environment/node/argv'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { parseArgs } from 'vs/platform/environment/node/argv'; import { dirname } from 'vs/base/common/resources'; +class TestBackupEnvironmentService extends WorkbenchEnvironmentService { + + constructor(private _appSettingsHome: URI) { + super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); + } + + get appSettingsHome() { return this._appSettingsHome; } + +} + interface Modifiers { metaKey?: boolean; ctrlKey?: boolean; @@ -59,14 +68,6 @@ interface Modifiers { shiftKey?: boolean; } -class SettingsTestEnvironmentService extends WorkbenchEnvironmentService { - constructor(args: ParsedArgs, _execPath: string, private _appSettingsHome: URI) { - super(args, _execPath); - } - - get appSettingsHome(): URI { return this._appSettingsHome; } -} - suite('KeybindingsEditing', () => { let instantiationService: TestInstantiationService; @@ -80,7 +81,7 @@ suite('KeybindingsEditing', () => { instantiationService = new TestInstantiationService(); - const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, URI.file(testDir)); + const environmentService = new TestBackupEnvironmentService(URI.file(testDir)); instantiationService.stub(IEnvironmentService, environmentService); instantiationService.stub(IConfigurationService, ConfigurationService); instantiationService.stub(IConfigurationService, 'getValue', { 'eol': '\n' }); @@ -98,8 +99,9 @@ suite('KeybindingsEditing', () => { instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.registerProvider(Schemas.userData, new UserDataFileSystemProvider(dirname(environmentService.keybindingsResource), new FileUserDataProvider(environmentService.appSettingsHome, fileService))); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, dirname(environmentService.appSettingsHome), diskFileSystemProvider)); instantiationService.stub(IFileService, fileService); instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 0640812db208cd3772c183bfe182322748565427..511daf8d2184d892fd1295b7a73262757b4ab42e 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -785,12 +785,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Check for locale file - if (isEqual(this.resource, joinPath(this.environmentService.appSettingsHome, 'locale.json'), !isLinux)) { + if (isEqual(this.resource, joinPath(this.environmentService.userRoamingDataHome, 'locale.json'), !isLinux)) { return 'locale'; } // Check for snippets - if (isEqualOrParent(this.resource, joinPath(this.environmentService.appSettingsHome, 'snippets'))) { + if (isEqualOrParent(this.resource, joinPath(this.environmentService.userRoamingDataHome, 'snippets'))) { return 'snippets'; } diff --git a/src/vs/workbench/services/textfile/node/textFileService.ts b/src/vs/workbench/services/textfile/node/textFileService.ts index 242aadab9873c04943a5732ee676be1ab3f30775..91fbdc6232e872dc7e2a4a56f074a81c70d7c628 100644 --- a/src/vs/workbench/services/textfile/node/textFileService.ts +++ b/src/vs/workbench/services/textfile/node/textFileService.ts @@ -390,7 +390,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { const defaultEncodingOverrides: IEncodingOverride[] = []; // Global settings - defaultEncodingOverrides.push({ parent: this.environmentService.appSettingsHome, encoding: UTF8 }); + defaultEncodingOverrides.push({ parent: this.environmentService.userRoamingDataHome, encoding: UTF8 }); // Workspace files defaultEncodingOverrides.push({ extension: WORKSPACE_EXTENSION, encoding: UTF8 }); diff --git a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts index b842b0f716568d3769823aa43f2b26c83a7e6697..d4dd8d7abb7302c3bec2b2525cb00163ad3fccac 100644 --- a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts @@ -4,79 +4,134 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; -import { IFileService, FileChangesEvent } from 'vs/platform/files/common/files'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; -import { VSBuffer } from 'vs/base/common/buffer'; import { startsWith } from 'vs/base/common/strings'; +import { BACKUPS } from 'vs/platform/environment/common/environment'; +import { Schemas } from 'vs/base/common/network'; -export class FileUserDataProvider extends Disposable implements IUserDataProvider { +export class FileUserDataProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability { - private _onDidChangeFile: Emitter = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; + readonly capabilities: FileSystemProviderCapabilities = this.fileSystemProvider.capabilities; + readonly onDidChangeCapabilities: Event = Event.None; + + private readonly _onDidChangeFile: Emitter = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; constructor( private readonly userDataHome: URI, - @IFileService private readonly fileService: IFileService + private readonly backupsHome: URI, + private readonly fileSystemProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, ) { super(); + // Assumption: This path always exists - this.fileService.watch(this.userDataHome); + this._register(this.fileSystemProvider.watch(this.userDataHome, { recursive: false, excludes: [] })); + this._register(this.fileSystemProvider.onDidChangeFile(e => this.handleFileChanges(e))); + } - this._register(this.fileService.onFileChanges(e => this.handleFileChanges(e))); + watch(resource: URI, opts: IWatchOptions): IDisposable { + return this.fileSystemProvider.watch(this.toFileSystemResource(resource), opts); } - private handleFileChanges(event: FileChangesEvent): void { - const changedPaths: string[] = []; - for (const change of event.changes) { - if (change.resource.scheme === this.userDataHome.scheme) { - const path = this.toPath(change.resource); - if (path) { - changedPaths.push(path); - } - } + stat(resource: URI): Promise { + return this.fileSystemProvider.stat(this.toFileSystemResource(resource)); + } + + mkdir(resource: URI): Promise { + return this.fileSystemProvider.mkdir(this.toFileSystemResource(resource)); + } + + rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { + return this.fileSystemProvider.rename(this.toFileSystemResource(from), this.toFileSystemResource(to), opts); + } + + readFile(resource: URI): Promise { + if (hasReadWriteCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.readFile(this.toFileSystemResource(resource)); } - if (changedPaths.length) { - this._onDidChangeFile.fire(changedPaths); + throw new Error('not supported'); + } + + readdir(resource: URI): Promise<[string, FileType][]> { + return this.fileSystemProvider.readdir(this.toFileSystemResource(resource)); + } + + writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + if (hasReadWriteCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.writeFile(this.toFileSystemResource(resource), content, opts); } + throw new Error('not supported'); } - async readFile(path: string): Promise { - const resource = this.toResource(path); - try { - const content = await this.fileService.readFile(resource); - return content.value.buffer; - } catch (e) { - const exists = await this.fileService.exists(resource); - if (exists) { - throw e; - } + open(resource: URI, opts: FileOpenOptions): Promise { + if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.open(this.toFileSystemResource(resource), opts); } - return VSBuffer.fromString('').buffer; + throw new Error('not supported'); } - writeFile(path: string, value: Uint8Array): Promise { - return this.fileService.writeFile(this.toResource(path), VSBuffer.wrap(value)).then(() => undefined); + close(fd: number): Promise { + if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.close(fd); + } + throw new Error('not supported'); + } + + read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.read(fd, pos, data, offset, length); + } + throw new Error('not supported'); + } + + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + if (hasOpenReadWriteCloseCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.write(fd, pos, data, offset, length); + } + throw new Error('not supported'); } - async listFiles(path: string): Promise { - const result = await this.fileService.resolve(this.toResource(path)); - return result.children ? result.children.map(c => this.toPath(c.resource)!) : []; + delete(resource: URI, opts: FileDeleteOptions): Promise { + return this.fileSystemProvider.delete(this.toFileSystemResource(resource), opts); } - deleteFile(path: string): Promise { - return this.fileService.del(this.toResource(path)); + private handleFileChanges(changes: IFileChange[]): void { + const userDataChanges: IFileChange[] = []; + for (const change of changes) { + const userDataResource = this.toUserDataResource(change.resource); + if (userDataResource) { + userDataChanges.push({ + resource: userDataResource, + type: change.type + }); + } + } + if (userDataChanges.length) { + this._onDidChangeFile.fire(userDataChanges); + } } - private toResource(path: string): URI { - return resources.joinPath(this.userDataHome, path); + private toFileSystemResource(userDataResource: URI): URI { + const fileSystemResource = userDataResource.with({ scheme: this.userDataHome.scheme }); + const relativePath = resources.relativePath(this.userDataHome, fileSystemResource); + if (relativePath && startsWith(relativePath, BACKUPS)) { + return resources.joinPath(resources.dirname(this.backupsHome), relativePath); + } + return fileSystemResource; } - private toPath(resource: URI): string | undefined { - const resourcePath = resource.toString(); - const userDataHomePath = this.userDataHome.toString(); - return startsWith(resourcePath, userDataHomePath) ? resourcePath.substr(userDataHomePath.length + 1) : undefined; + private toUserDataResource(fileSystemResource: URI): URI | null { + if (resources.relativePath(this.userDataHome, fileSystemResource)) { + return fileSystemResource.with({ scheme: Schemas.userData }); + } + const relativePath = resources.relativePath(this.backupsHome, fileSystemResource); + if (relativePath) { + return resources.joinPath(this.userDataHome, BACKUPS, relativePath).with({ scheme: Schemas.userData }); + } + return null; } + } \ No newline at end of file diff --git a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts index 3e477e0a0f4e0530104212070be72ca8f7b9839a..9693eaa5e00cad3d87a7627de12e0f278aa6e538 100644 --- a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts @@ -5,14 +5,15 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; +import { IUserDataProvider, FileChangeEvent } from 'vs/workbench/services/userData/common/userData'; import { VSBuffer } from 'vs/base/common/buffer'; +import { FileChangeType } from 'vs/platform/files/common/files'; export class InMemoryUserDataProvider extends Disposable implements IUserDataProvider { _serviceBrand: any; - private _onDidChangeFile: Emitter = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; + private _onDidChangeFile: Emitter = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; private readonly store: Map = new Map(); @@ -26,29 +27,25 @@ export class InMemoryUserDataProvider extends Disposable implements IUserDataPro } async readFile(path: string): Promise { - return VSBuffer.fromString(this.getValue(path)).buffer; + if (this.store.has(path)) { + return VSBuffer.fromString(this.store.get(path)!).buffer; + } + throw new Error(`Not Found: ${path}`); } async writeFile(path: string, value: Uint8Array): Promise { + const exists = this.store.has(path); const content = VSBuffer.wrap(value).toString(); - if (content !== this.getValue(path)) { - if (content) { - this.store.set(path, content); - this._onDidChangeFile.fire([path]); - } else { - this.deleteFile(path); - } + if (!exists || content !== this.store.get(path)) { + this.store.set(path, content); + this._onDidChangeFile.fire([{ path, type: exists ? FileChangeType.UPDATED : FileChangeType.ADDED }]); } } async deleteFile(path: string): Promise { if (this.store.has(path)) { this.store.delete(path); - this._onDidChangeFile.fire([path]); + this._onDidChangeFile.fire([{ path, type: FileChangeType.DELETED }]); } } - - private getValue(key: string): string { - return this.store.get(key) || ''; - } } \ No newline at end of file diff --git a/src/vs/workbench/services/userData/common/userData.ts b/src/vs/workbench/services/userData/common/userData.ts index dc448d2c9c5487857986f547b5f98c784b15c707..6ce93879fc3dd5b3ca0e95e82daebd2c8e14b1c1 100644 --- a/src/vs/workbench/services/userData/common/userData.ts +++ b/src/vs/workbench/services/userData/common/userData.ts @@ -4,6 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import { FileChangeType } from 'vs/platform/files/common/files'; + +/** + * The event user data providers must use to signal a file change. + */ +export interface FileChangeEvent { + + /** + * The type of change. + */ + readonly type: FileChangeType; + + /** + * The path of the file that has changed. + */ + readonly path: string; +} /** * The userDataProvider is used to handle user specific application @@ -27,10 +44,9 @@ import { Event } from 'vs/base/common/event'; export interface IUserDataProvider { /** - * Emitted when one ore more files are added, changed or deleted. The event provides - * an array of paths of these files. + * An event to signal that a file has been created, changed, or deleted. */ - readonly onDidChangeFile: Event; + readonly onDidChangeFile: Event; /** * Read the file contents of the given path. @@ -61,4 +77,4 @@ export interface IUserDataProvider { * Throw an error if the path does not exist or points to a file. */ listFiles(path: string): Promise; -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts b/src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts deleted file mode 100644 index ad1be9760c8a2246b0d46d798a3b9f0aa82dc61e..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts +++ /dev/null @@ -1,114 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { FileSystemProviderCapabilities, FileWriteOptions, IStat, FileType, FileDeleteOptions, IWatchOptions, FileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability, IFileChange, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; -import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; -import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; -import { TernarySearchTree } from 'vs/base/common/map'; -import { startsWith } from 'vs/base/common/strings'; - -export class UserDataFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { - - private readonly versions: Map = new Map(); - - readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; - readonly onDidChangeCapabilities: Event = Event.None; - - private readonly _onDidChangeFile: Emitter = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; - - - constructor( - private readonly userDataHome: URI, - private readonly userDataProvider: IUserDataProvider - ) { - super(); - } - - watch(resource: URI, opts: IWatchOptions): IDisposable { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalud user data resource ${resource}`); - } - return this.userDataProvider.onDidChangeFile(e => { - if (new UserDataChangesEvent(e).contains(path)) { - this.versions.set(path, (this.versions.get(path) || 1) + 1); - this._onDidChangeFile.fire(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }]).changes); - } - }); - } - - async stat(resource: URI): Promise { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalud user data resource ${resource}`); - } - return { - type: FileType.File, - ctime: 0, - mtime: this.versions.get(path) || 0, - size: 0 - }; - } - - mkdir(resource: URI): Promise { throw new Error('not supported'); } - delete(resource: URI, opts: FileDeleteOptions): Promise { throw new Error('not supported'); } - rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { throw new Error('not supported'); } - - async readFile(resource: URI): Promise { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalud user data resource ${resource}`); - } - return this.userDataProvider.readFile(path); - } - - async readdir(resource: URI): Promise<[string, FileType][]> { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalud user data resource ${resource}`); - } - const children = await this.userDataProvider.listFiles(path); - return children.map(c => [c, FileType.Unknown]); - } - - writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { - const path = this.toPath(resource); - if (!path) { - throw new Error(`Invalud user data resource ${resource}`); - } - return this.userDataProvider.writeFile(path, content); - } - - private toPath(resource: URI): string | undefined { - const resourcePath = resource.toString(); - const userDataHomePath = this.userDataHome.toString(); - return startsWith(resourcePath, userDataHomePath) ? resourcePath.substr(userDataHomePath.length + 1) : undefined; - } -} - -class UserDataChangesEvent { - - private _pathsTree: TernarySearchTree | undefined = undefined; - - constructor(readonly paths: string[]) { } - - private get pathsTree(): TernarySearchTree { - if (!this._pathsTree) { - this._pathsTree = TernarySearchTree.forPaths(); - for (const path of this.paths) { - this._pathsTree.set(path, path); - } - } - return this._pathsTree; - } - - contains(pathOrSegment: string): boolean { - return this.pathsTree.findSubstr(pathOrSegment) !== undefined; - } - -} \ No newline at end of file diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index a0615457e0a80baae2af1b62eb9e269f5bd0c1e2..e0b3b1ed9ad5cb55b70db8fdd12f1fbcee371868 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -6,7 +6,7 @@ import 'vs/workbench/workbench.web.main'; import { main } from 'vs/workbench/browser/web.main'; import { UriComponents } from 'vs/base/common/uri'; -import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; +import { IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; export interface IWorkbenchConstructionOptions { @@ -36,7 +36,7 @@ export interface IWorkbenchConstructionOptions { * Experimental: The userDataProvider is used to handle user specific application * state like settings, keybindings, UI state (e.g. opened editors) and snippets. */ - userDataProvider?: IUserDataProvider; + userDataProvider?: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability; } /**