diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 5de857d7575..6b11f100f36 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -22,67 +22,36 @@ import { equals } from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; import { IConfigurationModel, compare } from 'vs/platform/configuration/common/configuration'; import { createSHA1 } from 'vs/base/browser/hash'; - -export class LocalUserConfiguration extends Disposable { - - private readonly userConfigurationResource: URI; - private userConfiguration: NodeBasedUserConfiguration | FileServiceBasedUserConfiguration; - private changeDisposable: IDisposable = Disposable.None; - - private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - - constructor( - userConfigurationResource: URI, - configurationFileService: IConfigurationFileService - ) { - super(); - this.userConfigurationResource = userConfigurationResource; - this.userConfiguration = this._register(new NodeBasedUserConfiguration(this.userConfigurationResource, configurationFileService)); - } - - initialize(): Promise { - return this.userConfiguration.initialize(); - } - - reload(): Promise { - return this.userConfiguration.reload(); - } - - async adopt(fileService: IFileService): Promise { - if (this.userConfiguration instanceof NodeBasedUserConfiguration) { - const oldConfigurationModel = this.userConfiguration.getConfigurationModel(); - this.userConfiguration.dispose(); - dispose(this.changeDisposable); - - let newConfigurationModel = new ConfigurationModel(); - this.userConfiguration = this._register(new FileServiceBasedUserConfiguration(this.userConfigurationResource, fileService)); - this.changeDisposable = this._register(this.userConfiguration.onDidChangeConfiguration(configurationModel => this._onDidChangeConfiguration.fire(configurationModel))); - newConfigurationModel = await this.userConfiguration.initialize(); - - const { added, updated, removed } = compare(oldConfigurationModel, newConfigurationModel); - if (added.length > 0 || updated.length > 0 || removed.length > 0) { - return newConfigurationModel; - } - } - return null; - } -} +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class RemoteUserConfiguration extends Disposable { private readonly _cachedConfiguration: CachedUserConfiguration; - private _userConfiguration: FileServiceBasedUserConfiguration | CachedUserConfiguration; + private readonly _configurationFileService: IConfigurationFileService; + private _userConfiguration: UserConfiguration | CachedUserConfiguration; + private _userConfigurationDisposable: IDisposable = Disposable.None; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; constructor( remoteAuthority: string, - configurationCache: IConfigurationCache + configurationCache: IConfigurationCache, + configurationFileService: IConfigurationFileService, + remoteAgentService: IRemoteAgentService ) { super(); + this._configurationFileService = configurationFileService; this._userConfiguration = this._cachedConfiguration = new CachedUserConfiguration(remoteAuthority, configurationCache); + remoteAgentService.getEnvironment().then(environment => { + if (environment) { + this._userConfiguration.dispose(); + this._userConfigurationDisposable.dispose(); + this._userConfiguration = this._register(new UserConfiguration(environment.appSettingsPath, this._configurationFileService)); + this._userConfigurationDisposable = this._register(this._userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel))); + this._userConfiguration.initialize().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)); + } + }); } initialize(): Promise { @@ -93,24 +62,6 @@ export class RemoteUserConfiguration extends Disposable { return this._userConfiguration.reload(); } - async adopt(configurationResource: URI | null, fileService: IFileService): Promise { - if (this._userConfiguration instanceof CachedUserConfiguration) { - const oldConfigurationModel = this._userConfiguration.getConfigurationModel(); - let newConfigurationModel = new ConfigurationModel(); - if (configurationResource) { - this._userConfiguration = new FileServiceBasedUserConfiguration(configurationResource, fileService); - this._register(this._userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel))); - newConfigurationModel = await this._userConfiguration.initialize(); - } - const { added, updated, removed } = compare(oldConfigurationModel, newConfigurationModel); - if (added.length > 0 || updated.length > 0 || removed.length > 0) { - this.updateCache(newConfigurationModel); - return newConfigurationModel; - } - } - return null; - } - private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void { this.updateCache(configurationModel); this._onDidChangeConfiguration.fire(configurationModel); @@ -121,51 +72,7 @@ export class RemoteUserConfiguration extends Disposable { } } -class NodeBasedUserConfiguration extends Disposable { - - private configuraitonModel: ConfigurationModel = new ConfigurationModel(); - - constructor( - private readonly userConfigurationResource: URI, - private readonly configurationFileService: IConfigurationFileService - ) { - super(); - } - - initialize(): Promise { - return this._load(); - } - - reload(): Promise { - return this._load(); - } - - getConfigurationModel(): ConfigurationModel { - return this.configuraitonModel; - } - - async _load(): Promise { - const exists = await this.configurationFileService.exists(this.userConfigurationResource); - if (exists) { - try { - const content = await this.configurationFileService.resolveContent(this.userConfigurationResource); - const parser = new ConfigurationModelParser(this.userConfigurationResource.toString()); - parser.parse(content); - this.configuraitonModel = parser.configurationModel; - } catch (e) { - // ignore error - errors.onUnexpectedError(e); - this.configuraitonModel = new ConfigurationModel(); - } - } else { - this.configuraitonModel = new ConfigurationModel(); - } - return this.configuraitonModel; - } - -} - -export class FileServiceBasedUserConfiguration extends Disposable { +export class UserConfiguration extends Disposable { private readonly reloadConfigurationScheduler: RunOnceScheduler; protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); @@ -176,11 +83,11 @@ export class FileServiceBasedUserConfiguration extends Disposable { constructor( private readonly configurationResource: URI, - private readonly fileService: IFileService + private readonly configurationFileService: IConfigurationFileService ) { super(); - this._register(fileService.onFileChanges(e => this.handleFileEvents(e))); + this._register(configurationFileService.onFileChanges(e => this.handleFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); this._register(toDisposable(() => { this.stopWatchingResource(); @@ -189,7 +96,7 @@ export class FileServiceBasedUserConfiguration extends Disposable { } private watchResource(): void { - this.fileWatcherDisposable = this.fileService.watch(this.configurationResource); + this.fileWatcherDisposable = this.configurationFileService.watch(this.configurationResource); } private stopWatchingResource(): void { @@ -199,7 +106,7 @@ export class FileServiceBasedUserConfiguration extends Disposable { private watchDirectory(): void { const directory = resources.dirname(this.configurationResource); - this.directoryWatcherDisposable = this.fileService.watch(directory); + this.directoryWatcherDisposable = this.configurationFileService.watch(directory); } private stopWatchingDirectory(): void { @@ -208,22 +115,32 @@ export class FileServiceBasedUserConfiguration extends Disposable { } async initialize(): Promise { - const exists = await this.fileService.exists(this.configurationResource); + const exists = await this.configurationFileService.exists(this.configurationResource); this.onResourceExists(exists); - return this.reload(); + const configuraitonModel = await this.reload(); + this.configurationFileService.whenWatchingStarted.then(() => this.onWatchStarted(configuraitonModel)); + return configuraitonModel; } async reload(): Promise { try { - const content = await this.fileService.resolveContent(this.configurationResource); + const content = await this.configurationFileService.resolveContent(this.configurationResource); const parser = new ConfigurationModelParser(this.configurationResource.toString()); - parser.parse(content.value); + parser.parse(content); return parser.configurationModel; } catch (e) { return new ConfigurationModel(); } } + private async onWatchStarted(currentModel: ConfigurationModel): Promise { + const configuraitonModel = await this.reload(); + const { added, removed, updated } = compare(currentModel, configuraitonModel); + if (added.length || removed.length || updated.length) { + this._onDidChangeConfiguration.fire(configuraitonModel); + } + } + private async handleFileEvents(event: FileChangesEvent): Promise { const events = event.changes; diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 93ebca0816c..633a86cbad5 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -23,7 +23,7 @@ import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resour import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, LocalUserConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; +import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { localize } from 'vs/nls'; @@ -41,7 +41,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; private defaultConfiguration: DefaultConfigurationModel; - private localUserConfiguration: LocalUserConfiguration | null = null; + private localUserConfiguration: UserConfiguration | null = null; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; private cachedFolderConfigs: ResourceMap; @@ -67,7 +67,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic constructor( { userSettingsResource, remoteAuthority, configurationCache }: { userSettingsResource?: URI, remoteAuthority?: string, configurationCache: IConfigurationCache }, private readonly configurationFileService: IConfigurationFileService, - private readonly remoteAgentService: IRemoteAgentService, + remoteAgentService: IRemoteAgentService, ) { super(); @@ -75,11 +75,11 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this.defaultConfiguration = new DefaultConfigurationModel(); this.configurationCache = configurationCache; if (userSettingsResource) { - this.localUserConfiguration = this._register(new LocalUserConfiguration(userSettingsResource, configurationFileService)); + this.localUserConfiguration = this._register(new UserConfiguration(userSettingsResource, configurationFileService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); } if (remoteAuthority) { - this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache)); + this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, configurationFileService, remoteAgentService)); this._register(this.remoteUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onRemoteUserConfigurationChanged(userConfiguration))); } this.workspaceConfiguration = this._register(new WorkspaceConfiguration(configurationCache, this.configurationFileService)); @@ -296,15 +296,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic acquireFileService(fileService: IFileService): void { this.fileService = fileService; + this.configurationFileService.fileService = fileService; const changedWorkspaceFolders: IWorkspaceFolder[] = []; - if (this.localUserConfiguration) { - this.localUserConfiguration.adopt(fileService) - .then(changedModel => { - if (changedModel) { - this.onLocalUserConfigurationChanged(changedModel); - } - }); - } Promise.all([this.workspaceConfiguration.adopt(fileService), ...this.cachedFolderConfigs.values() .map(folderConfiguration => folderConfiguration.adopt(fileService) .then(result => { @@ -322,15 +315,6 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } this.releaseWorkspaceBarrier(); }); - if (this.remoteUserConfiguration) { - this.remoteAgentService.getEnvironment() - .then(environment => this.remoteUserConfiguration!.adopt(environment ? environment.appSettingsPath : null, fileService) - .then(changedModel => { - if (changedModel) { - this.onRemoteUserConfigurationChanged(changedModel); - } - })); - } } acquireInstantiationService(instantiationService: IInstantiationService): void { diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 8fdbde5b904..9304de85b07 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -4,6 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; export const FOLDER_CONFIG_FOLDER_NAME = '.vscode'; export const FOLDER_SETTINGS_NAME = 'settings'; @@ -34,6 +37,22 @@ export interface IConfigurationCache { } export interface IConfigurationFileService { + fileService: IFileService | null; + readonly onFileChanges: Event; + readonly whenWatchingStarted: Promise; + watch(resource: URI): IDisposable; exists(resource: URI): Promise; resolveContent(resource: URI): Promise; } + +export class ConfigurationFileService implements IConfigurationFileService { + + constructor(public fileService: IFileService) { } + + get onFileChanges() { return this.fileService.onFileChanges; } + readonly whenWatchingStarted: Promise = Promise.resolve(); + watch(resource: URI): IDisposable { return this.fileService.watch(resource); } + exists(resource: URI): Promise { return this.fileService.exists(resource); } + resolveContent(resource: URI): Promise { return this.fileService.resolveContent(resource, { encoding: 'utf8' }).then(content => content.value); } + +} diff --git a/src/vs/workbench/services/configuration/node/configurationFileService.ts b/src/vs/workbench/services/configuration/node/configurationFileService.ts index ef6d1b6ea35..630e2b6fe7b 100644 --- a/src/vs/workbench/services/configuration/node/configurationFileService.ts +++ b/src/vs/workbench/services/configuration/node/configurationFileService.ts @@ -4,18 +4,65 @@ *--------------------------------------------------------------------------------------------*/ import * as pfs from 'vs/base/node/pfs'; -import { IConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; +import { IConfigurationFileService, ConfigurationFileService as FileServiceBasedConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; +import { Event, Emitter } from 'vs/base/common/event'; +import { FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Barrier } from 'vs/base/common/async'; -export class ConfigurationFileService implements IConfigurationFileService { +export class ConfigurationFileService extends Disposable implements IConfigurationFileService { + + private _fileServiceBasedConfigurationFileService: FileServiceBasedConfigurationFileService | null = null; + + private readonly _onFileChanges: Emitter = this._register(new Emitter()); + readonly onFileChanges: Event = this._onFileChanges.event; + + private readonly _watchReadyBarrier = new Barrier(); + readonly whenWatchingStarted: Promise = this._watchReadyBarrier.wait().then(() => undefined); + + private _watchResources: { resource: URI, disposable: { disposable: IDisposable | null } }[] = []; + + watch(resource: URI): IDisposable { + if (this._fileServiceBasedConfigurationFileService) { + return this._fileServiceBasedConfigurationFileService.watch(resource); + } + const disposable: { disposable: IDisposable | null } = { disposable: null }; + this._watchResources.push({ resource, disposable }); + return toDisposable(() => { + if (disposable.disposable) { + disposable.disposable.dispose(); + } + }); + } exists(resource: URI): Promise { - return pfs.exists(resource.fsPath); + return this._fileServiceBasedConfigurationFileService ? this._fileServiceBasedConfigurationFileService.exists(resource) : pfs.exists(resource.fsPath); } async resolveContent(resource: URI): Promise { - const contents = await pfs.readFile(resource.fsPath); - return contents.toString(); + if (this._fileServiceBasedConfigurationFileService) { + return this._fileServiceBasedConfigurationFileService.resolveContent(resource); + } else { + const contents = await pfs.readFile(resource.fsPath); + return contents.toString(); + } } + private _fileService: IFileService | null; + get fileService(): IFileService | null { + return this._fileService; + } + + set fileService(fileService: IFileService | null) { + if (fileService && !this._fileServiceBasedConfigurationFileService) { + this._fileService = fileService; + this._watchReadyBarrier.open(); + this._fileServiceBasedConfigurationFileService = new FileServiceBasedConfigurationFileService(fileService); + this._register(this._fileServiceBasedConfigurationFileService.onFileChanges(e => this._onFileChanges.fire(e))); + for (const { resource, disposable } of this._watchResources) { + disposable.disposable = this._fileServiceBasedConfigurationFileService.watch(resource); + } + } + } }