diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index da1b56c7ee9f6b78d26785fd0c1d9bec31f5f189..8e93c0d144f16355a769bcdc8de648dd3bd21d36 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -337,11 +337,13 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH console[severity].apply(console, ...messages); } - async showItemInFolder(path: URI): Promise { + async showItemInFolder(resource: URI): Promise { this.logService.trace('windowsService#showItemInFolder'); - if (path.scheme === Schemas.file) { - shell.showItemInFolder(path.fsPath); + if (resource.scheme === Schemas.file) { + shell.showItemInFolder(resource.fsPath); + } else if (resource.scheme === Schemas.userData) { + shell.showItemInFolder(resource.path); } } diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 193b9d2e15b85cf17ff1396d11c26716b711b756..101acd25023d20a56a1f311bb4a6203baaff3fa3 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -200,6 +200,7 @@ const copyRelativePathCommand = { appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.isEqualTo(Schemas.userData))); appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFileSystemResource); function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr, group?: string): void { diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 6fd5e06278543861fec6f7e53a123525ebc392fa..6c125ff67f6e328640675148f020f5cc09e4e950 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; +import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES, ConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; @@ -24,11 +24,51 @@ import { IConfigurationModel } from 'vs/platform/configuration/common/configurat import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { hash } from 'vs/base/common/hash'; +export class UserConfiguration extends Disposable { + + private readonly parser: ConfigurationModelParser; + private readonly reloadConfigurationScheduler: RunOnceScheduler; + protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + + constructor( + private readonly userSettingsResource: URI, + private readonly scopes: ConfigurationScope[] | undefined, + private readonly fileService: IFileService + ) { + super(); + + 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())); + } + + async initialize(): Promise { + return this.reload(); + } + + async reload(): Promise { + try { + const content = await this.fileService.readFile(this.userSettingsResource); + this.parser.parseContent(content.value.toString() || '{}'); + return this.parser.configurationModel; + } catch (e) { + return new ConfigurationModel(); + } + } + + reprocess(): ConfigurationModel { + this.parser.parse(); + return this.parser.configurationModel; + } +} + export class RemoteUserConfiguration extends Disposable { private readonly _cachedConfiguration: CachedRemoteUserConfiguration; private readonly _configurationFileService: ConfigurationFileService; - private _userConfiguration: UserConfiguration | CachedRemoteUserConfiguration; + private _userConfiguration: FileServiceBasedRemoteUserConfiguration | CachedRemoteUserConfiguration; private _userConfigurationInitializationPromise: Promise | null = null; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); @@ -45,7 +85,7 @@ export class RemoteUserConfiguration extends Disposable { this._userConfiguration = this._cachedConfiguration = new CachedRemoteUserConfiguration(remoteAuthority, configurationCache); remoteAgentService.getEnvironment().then(async environment => { if (environment) { - const userConfiguration = this._register(new UserConfiguration(environment.settingsPath, REMOTE_MACHINE_SCOPES, this._configurationFileService)); + const userConfiguration = this._register(new FileServiceBasedRemoteUserConfiguration(environment.settingsPath, REMOTE_MACHINE_SCOPES, this._configurationFileService)); this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel))); this._userConfigurationInitializationPromise = userConfiguration.initialize(); const configurationModel = await this._userConfigurationInitializationPromise; @@ -57,7 +97,7 @@ export class RemoteUserConfiguration extends Disposable { } async initialize(): Promise { - if (this._userConfiguration instanceof UserConfiguration) { + if (this._userConfiguration instanceof FileServiceBasedRemoteUserConfiguration) { return this._userConfiguration.initialize(); } @@ -90,7 +130,7 @@ export class RemoteUserConfiguration extends Disposable { } } -export class UserConfiguration extends Disposable { +class FileServiceBasedRemoteUserConfiguration extends Disposable { private readonly parser: ConfigurationModelParser; private readonly reloadConfigurationScheduler: RunOnceScheduler; diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 769c02a678d39f11ab8c9783775a71e9c445ffc8..b2b2f3469f920ce926d97b1666aa75cb463ad2af 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -81,7 +81,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this.configurationFileService = new ConfigurationFileService(fileService); this._configuration = new Configuration(this.defaultConfiguration, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); this.cachedFolderConfigs = new ResourceMap(); - this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, this.configurationFileService)); + this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); if (remoteAuthority) { this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, this.configurationFileService, remoteAgentService)); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index e2578dd1a1b54d5a0605eef8cabaa6ef585bd610..97213c6b7dc7cb9a403d800c76aa82e97f0bf4ce 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -71,9 +71,9 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { this.configuration.remoteAuthority = configuration.remoteAuthority; if (remoteUserDataUri) { - this.appSettingsHome = remoteUserDataUri || URI.file('/User').with({ scheme: Schemas.userData }); - this.settingsResource = joinPath(this.appSettingsHome, 'settings.json'); - this.keybindingsResource = joinPath(this.appSettingsHome, 'keybindings.json'); + 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'); diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/node/environmentService.ts index db2a229c03bafa33948553c922627b29f64caf69..bb72c8785b8c56e4b95945320700457b34648bff 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/node/environmentService.ts @@ -6,6 +6,10 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; 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 { @@ -21,4 +25,10 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I get configuration(): IWindowConfiguration { return this._configuration; } + + @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 }); } } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index fcd949b0b0541a1a4c169a50ef5ef62b956db8da..60509939c874cb60874a353ab98610bfd2d7fddf 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -36,11 +36,10 @@ import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; // tslint:disable-next-line: import-patterns import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; -import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; -import { dirname, isEqual } from 'vs/base/common/resources'; +import { IFileService } from 'vs/platform/files/common/files'; import { parse } from 'vs/base/common/json'; import * as objects from 'vs/base/common/objects'; import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapInfo'; @@ -560,12 +559,11 @@ class UserKeybindings extends Disposable { private _keybindings: IUserFriendlyKeybinding[] = []; get keybindings(): IUserFriendlyKeybinding[] { return this._keybindings; } + 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 readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; constructor( private readonly keybindingsResource: URI, @@ -573,40 +571,16 @@ class UserKeybindings extends Disposable { ) { super(); - 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(); - })); - } - - private watchResource(): void { - this.fileWatcherDisposable = this.fileService.watch(this.keybindingsResource); - } - - private stopWatchingResource(): void { - this.fileWatcherDisposable.dispose(); - this.fileWatcherDisposable = Disposable.None; - } - - private watchDirectory(): void { - const directory = dirname(this.keybindingsResource); - this.directoryWatcherDisposable = this.fileService.watch(directory); - } - - private stopWatchingDirectory(): void { - this.directoryWatcherDisposable.dispose(); - this.directoryWatcherDisposable = Disposable.None; + this._register(this.fileService.watch(this.keybindingsResource)); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.keybindingsResource))(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { - const exists = await this.fileService.exists(this.keybindingsResource); - this.onResourceExists(exists); await this.reload(); } @@ -621,39 +595,6 @@ class UserKeybindings extends Disposable { } return existing ? !objects.equals(existing, this._keybindings) : true; } - - 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.keybindingsResource, 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(); - } - } } let schemaId = 'vscode://schemas/keybindings'; diff --git a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts index 3be33dbd042854fe9e7efbe59b69ed2d960c5ef4..b842b0f716568d3769823aa43f2b26c83a7e6697 100644 --- a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts @@ -10,6 +10,7 @@ import { IFileService, FileChangesEvent } 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'; export class FileUserDataProvider extends Disposable implements IUserDataProvider { @@ -28,17 +29,17 @@ export class FileUserDataProvider extends Disposable implements IUserDataProvide } private handleFileChanges(event: FileChangesEvent): void { - const changedKeys: string[] = []; + const changedPaths: string[] = []; for (const change of event.changes) { if (change.resource.scheme === this.userDataHome.scheme) { - const key = this.toKey(change.resource); - if (key) { - changedKeys.push(key); + const path = this.toPath(change.resource); + if (path) { + changedPaths.push(path); } } } - if (changedKeys.length) { - this._onDidChangeFile.fire(changedKeys); + if (changedPaths.length) { + this._onDidChangeFile.fire(changedPaths); } } @@ -62,18 +63,20 @@ export class FileUserDataProvider extends Disposable implements IUserDataProvide async listFiles(path: string): Promise { const result = await this.fileService.resolve(this.toResource(path)); - return result.children ? result.children.map(c => this.toKey(c.resource)!) : []; + return result.children ? result.children.map(c => this.toPath(c.resource)!) : []; } deleteFile(path: string): Promise { return this.fileService.del(this.toResource(path)); } - private toResource(key: string): URI { - return resources.joinPath(this.userDataHome, ...key.split('/')); + private toResource(path: string): URI { + return resources.joinPath(this.userDataHome, path); } - private toKey(resource: URI): string | undefined { - return resources.relativePath(this.userDataHome, resource); + 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; } } \ 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 index d6f10cbf477bcf488998b477a3956a86e63e0e04..ad1be9760c8a2246b0d46d798a3b9f0aa82dc61e 100644 --- a/src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts +++ b/src/vs/workbench/services/userData/common/userDataFileSystemProvider.ts @@ -8,8 +8,8 @@ import { FileSystemProviderCapabilities, FileWriteOptions, IStat, FileType, File 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 * as resources from 'vs/base/common/resources'; import { TernarySearchTree } from 'vs/base/common/map'; +import { startsWith } from 'vs/base/common/strings'; export class UserDataFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { @@ -85,7 +85,9 @@ export class UserDataFileSystemProvider extends Disposable implements IFileSyste } private toPath(resource: URI): string | undefined { - return resources.relativePath(this.userDataHome, resource); + const resourcePath = resource.toString(); + const userDataHomePath = this.userDataHome.toString(); + return startsWith(resourcePath, userDataHomePath) ? resourcePath.substr(userDataHomePath.length + 1) : undefined; } } @@ -105,8 +107,8 @@ class UserDataChangesEvent { return this._pathsTree; } - contains(keyOrSegment: string): boolean { - return this.pathsTree.findSubstr(keyOrSegment) !== undefined; + contains(pathOrSegment: string): boolean { + return this.pathsTree.findSubstr(pathOrSegment) !== undefined; } } \ No newline at end of file