diff --git a/src/vs/platform/configuration/common/userConfigurationFileService.ts b/src/vs/platform/configuration/common/userConfigurationFileService.ts new file mode 100644 index 0000000000000..c826c7fbd87cd --- /dev/null +++ b/src/vs/platform/configuration/common/userConfigurationFileService.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Queue } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { JSONPath, parse, ParseError } from 'vs/base/common/json'; +import { setProperty } from 'vs/base/common/jsonEdit'; +import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; + +export const enum UserConfigurationErrorCode { + ERROR_INVALID_FILE = 'ERROR_INVALID_FILE', + ERROR_FILE_MODIFIED_SINCE = 'ERROR_FILE_MODIFIED_SINCE' +} + +export interface IJSONValue { + path: JSONPath; + value: any; +} + +export const UserConfigurationFileServiceId = 'IUserConfigurationFileService'; +export const IUserConfigurationFileService = createDecorator(UserConfigurationFileServiceId); + +export interface IUserConfigurationFileService { + readonly _serviceBrand: undefined; + + updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise; +} + +export class UserConfigurationFileService implements IUserConfigurationFileService { + + readonly _serviceBrand: undefined; + + private readonly queue: Queue; + + constructor( + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + this.queue = new Queue(); + } + + async updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise { + return this.queue.queue(() => this.doWrite(this.environmentService.settingsResource, value, formattingOptions)); // queue up writes to prevent race conditions + } + + private async doWrite(resource: URI, jsonValue: IJSONValue, formattingOptions: FormattingOptions): Promise { + this.logService.trace(`${UserConfigurationFileServiceId}#write`, resource.toString(), jsonValue); + const { value, mtime, etag } = await this.fileService.readFile(resource, { atomic: true }); + let content = value.toString(); + + const parseErrors: ParseError[] = []; + parse(content, parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); + if (parseErrors.length) { + throw new Error(UserConfigurationErrorCode.ERROR_INVALID_FILE); + } + + const edit = this.getEdits(jsonValue, content, formattingOptions)[0]; + if (edit) { + content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); + try { + await this.fileService.writeFile(resource, VSBuffer.fromString(content), { etag, mtime }); + } catch (error) { + if ((error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { + throw new Error(UserConfigurationErrorCode.ERROR_FILE_MODIFIED_SINCE); + } + } + } + } + + private getEdits({ value, path }: IJSONValue, modelContent: string, formattingOptions: FormattingOptions): Edit[] { + if (path.length) { + return setProperty(modelContent, path, value, formattingOptions); + } + + // Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify + const content = JSON.stringify(value, null, formattingOptions.insertSpaces && formattingOptions.tabSize ? ' '.repeat(formattingOptions.tabSize) : '\t'); + return [{ + content, + length: modelContent.length, + offset: 0 + }]; + } +} + diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 7c859c5be5130..863f113569428 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -8,27 +8,28 @@ import { URI } from 'vs/base/common/uri'; import * as json from 'vs/base/common/json'; import { setProperty } from 'vs/base/common/jsonEdit'; import { Queue } from 'vs/base/common/async'; -import { Edit } from 'vs/base/common/jsonFormatter'; -import { IReference } from 'vs/base/common/lifecycle'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS, TASKS_DEFAULT } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ITextModel } from 'vs/editor/common/model'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { withUndefinedAsNull, withNullAsUndefined } from 'vs/base/common/types'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IUserConfigurationFileService, UserConfigurationErrorCode } from 'vs/platform/configuration/common/userConfigurationFileService'; +import { ITextModel } from 'vs/editor/common/model'; +import { IReference } from 'vs/base/common/lifecycle'; +import { Range } from 'vs/editor/common/core/range'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Selection } from 'vs/editor/common/core/selection'; export const enum ConfigurationEditingErrorCode { @@ -105,10 +106,6 @@ export interface IConfigurationValue { } export interface IConfigurationEditingOptions { - /** - * If `true`, do not saves the configuration. Default is `false`. - */ - donotSave?: boolean; /** * If `true`, do not notifies the error to user by showing the message box. Default is `false`. */ @@ -131,11 +128,10 @@ interface IConfigurationEditOperation extends IConfigurationValue { jsonPath: json.JSONPath; resource?: URI; workspaceStandAloneConfigurationKey?: string; - } interface ConfigurationEditingOptions extends IConfigurationEditingOptions { - force?: boolean; + ignoreDirtyFile?: boolean; } export class ConfigurationEditingService { @@ -156,7 +152,8 @@ export class ConfigurationEditingService { @IPreferencesService private readonly preferencesService: IPreferencesService, @IEditorService private readonly editorService: IEditorService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IUserConfigurationFileService private readonly userConfigurationFileService: IUserConfigurationFileService, ) { this.queue = new Queue(); remoteAgentService.getEnvironment().then(environment => { @@ -179,15 +176,22 @@ export class ConfigurationEditingService { } private async doWriteConfiguration(operation: IConfigurationEditOperation, options: ConfigurationEditingOptions): Promise { - const checkDirtyConfiguration = !(options.force || options.donotSave); - const saveConfiguration = options.force || !options.donotSave; - const reference = await this.resolveAndValidate(operation.target, operation, checkDirtyConfiguration, options.scopes || {}); + await this.validate(operation.target, operation, !options.ignoreDirtyFile, options.scopes || {}); + const resource: URI = operation.resource!; + const reference = await this.resolveModelReference(resource); try { - await this.writeToBuffer(reference.object.textEditorModel, operation, saveConfiguration); + const formattingOptions = this.getFormattingOptions(reference.object.textEditorModel); + if (this.uriIdentityService.extUri.isEqual(resource, this.environmentService.settingsResource)) { + await this.userConfigurationFileService.updateSettings({ path: operation.jsonPath, value: operation.value }, formattingOptions); + } else { + await this.updateConfiguration(operation, reference.object.textEditorModel, formattingOptions); + } } catch (error) { - if ((error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { - await this.textFileService.revert(operation.resource!); - return this.reject(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_MODIFIED_SINCE, operation.target, operation); + if ((error).message === UserConfigurationErrorCode.ERROR_INVALID_FILE) { + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, operation.target, operation); + } + if ((error).message === UserConfigurationErrorCode.ERROR_FILE_MODIFIED_SINCE || (error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_MODIFIED_SINCE, operation.target, operation); } throw error; } finally { @@ -195,10 +199,14 @@ export class ConfigurationEditingService { } } - private async writeToBuffer(model: ITextModel, operation: IConfigurationEditOperation, save: boolean): Promise { - const edit = this.getEdits(model, operation)[0]; - if (edit && this.applyEditsToBuffer(edit, model) && save) { - await this.textFileService.save(operation.resource!, { skipSaveParticipants: true /* programmatic change */, ignoreErrorHandler: true /* handle error self */ }); + private async updateConfiguration(operation: IConfigurationEditOperation, model: ITextModel, formattingOptions: FormattingOptions): Promise { + if (this.hasParseErrors(model.getValue(), operation)) { + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, operation.target, operation); + } + + const edit = this.getEdits(operation, model.getValue(), formattingOptions)[0]; + if (edit && this.applyEditsToBuffer(edit, model)) { + await this.textFileService.save(model.uri); } } @@ -215,6 +223,26 @@ export class ConfigurationEditingService { return false; } + private getEdits({ value, jsonPath }: IConfigurationEditOperation, modelContent: string, formattingOptions: FormattingOptions): Edit[] { + if (jsonPath.length) { + return setProperty(modelContent, jsonPath, value, formattingOptions); + } + + // Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify + const content = JSON.stringify(value, null, formattingOptions.insertSpaces && formattingOptions.tabSize ? ' '.repeat(formattingOptions.tabSize) : '\t'); + return [{ + content, + length: modelContent.length, + offset: 0 + }]; + } + + private getFormattingOptions(model: ITextModel): FormattingOptions { + const { insertSpaces, tabSize } = model.getOptions(); + const eol = model.getEOL(); + return { insertSpaces, tabSize, eol }; + } + private async onError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides | undefined): Promise { switch (error.code) { case ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION: @@ -261,7 +289,7 @@ export class ConfigurationEditingService { label: nls.localize('saveAndRetry', "Save and Retry"), run: () => { const key = operation.key ? `${operation.workspaceStandAloneConfigurationKey}.${operation.key}` : operation.workspaceStandAloneConfigurationKey!; - this.writeConfiguration(operation.target, { key, value: operation.value }, { force: true, scopes }); + this.writeConfiguration(operation.target, { key, value: operation.value }, { ignoreDirtyFile: true, scopes }); } }, { @@ -273,7 +301,7 @@ export class ConfigurationEditingService { this.notificationService.prompt(Severity.Error, error.message, [{ label: nls.localize('saveAndRetry', "Save and Retry"), - run: () => this.writeConfiguration(operation.target, { key: operation.key, value: operation.value }, { force: true, scopes }) + run: () => this.writeConfiguration(operation.target, { key: operation.key, value: operation.value }, { ignoreDirtyFile: true, scopes }) }, { label: nls.localize('open', "Open Settings"), @@ -309,10 +337,9 @@ export class ConfigurationEditingService { this.editorService.openEditor({ resource, options: { pinned: true } }); } - private reject(code: ConfigurationEditingErrorCode, target: EditableConfigurationTarget, operation: IConfigurationEditOperation): Promise { + private toConfigurationEditingError(code: ConfigurationEditingErrorCode, target: EditableConfigurationTarget, operation: IConfigurationEditOperation): ConfigurationEditingError { const message = this.toErrorMessage(code, target, operation); - - return Promise.reject(new ConfigurationEditingError(message, code)); + return new ConfigurationEditingError(message, code); } private toErrorMessage(error: ConfigurationEditingErrorCode, target: EditableConfigurationTarget, operation: IConfigurationEditOperation): string { @@ -419,24 +446,6 @@ export class ConfigurationEditingService { } } - private getEdits(model: ITextModel, edit: IConfigurationEditOperation): Edit[] { - const { tabSize, insertSpaces } = model.getOptions(); - const eol = model.getEOL(); - const { value, jsonPath } = edit; - - // Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify - if (!jsonPath.length) { - const content = JSON.stringify(value, null, insertSpaces ? ' '.repeat(tabSize) : '\t'); - return [{ - content, - length: model.getValue().length, - offset: 0 - }]; - } - - return setProperty(model.getValue(), jsonPath, value, { tabSize, insertSpaces, eol }); - } - private defaultResourceValue(resource: URI): string { const basename: string = this.uriIdentityService.extUri.basename(resource); const configurationValue: string = basename.substr(0, basename.length - this.uriIdentityService.extUri.extname(resource).length); @@ -454,60 +463,60 @@ export class ConfigurationEditingService { return this.textModelResolverService.createModelReference(resource); } - private hasParseErrors(model: ITextModel, operation: IConfigurationEditOperation): boolean { + private hasParseErrors(content: string, operation: IConfigurationEditOperation): boolean { // If we write to a workspace standalone file and replace the entire contents (no key provided) // we can return here because any parse errors can safely be ignored since all contents are replaced if (operation.workspaceStandAloneConfigurationKey && !operation.key) { return false; } const parseErrors: json.ParseError[] = []; - json.parse(model.getValue(), parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); + json.parse(content, parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); return parseErrors.length > 0; } - private resolveAndValidate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationOverrides): Promise> { + private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationOverrides): Promise { // Any key must be a known setting from the registry (unless this is a standalone config) if (!operation.workspaceStandAloneConfigurationKey) { const validKeys = this.configurationService.keys().default; if (validKeys.indexOf(operation.key) < 0 && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { - return this.reject(ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, target, operation); } } if (operation.workspaceStandAloneConfigurationKey) { // Global launches are not supported if ((operation.workspaceStandAloneConfigurationKey !== TASKS_CONFIGURATION_KEY) && (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE)) { - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET, target, operation); } } // Target cannot be workspace or folder if no workspace opened if ((target === EditableConfigurationTarget.WORKSPACE || target === EditableConfigurationTarget.WORKSPACE_FOLDER) && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - return this.reject(ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED, target, operation); } if (target === EditableConfigurationTarget.WORKSPACE) { if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[operation.key].scope === ConfigurationScope.APPLICATION) { - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation); } if (configurationProperties[operation.key].scope === ConfigurationScope.MACHINE) { - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE, target, operation); } } } if (target === EditableConfigurationTarget.WORKSPACE_FOLDER) { if (!operation.resource) { - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (!(configurationProperties[operation.key].scope === ConfigurationScope.RESOURCE || configurationProperties[operation.key].scope === ConfigurationScope.LANGUAGE_OVERRIDABLE)) { - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); } } } @@ -515,30 +524,18 @@ export class ConfigurationEditingService { if (overrides.overrideIdentifier) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[operation.key].scope !== ConfigurationScope.LANGUAGE_OVERRIDABLE) { - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION, target, operation); } } if (!operation.resource) { - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } - return this.resolveModelReference(operation.resource) - .then(reference => { - const model = reference.object.textEditorModel; - - if (this.hasParseErrors(model, operation)) { - reference.dispose(); - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, target, operation); - } + if (checkDirty && this.textFileService.isDirty(operation.resource)) { + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation); + } - // Target cannot be dirty if not writing into buffer - if (checkDirty && operation.resource && this.textFileService.isDirty(operation.resource)) { - reference.dispose(); - return this.reject(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation); - } - return reference; - }); } private getConfigurationEditOperation(target: EditableConfigurationTarget, config: IConfigurationValue, overrides: IConfigurationOverrides): IConfigurationEditOperation { diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 22a7e65d28ffd..b7f45b74fbdef 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -40,6 +40,7 @@ import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; +import { IUserConfigurationFileService, UserConfigurationFileService } from 'vs/platform/configuration/common/userConfigurationFileService'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -100,6 +101,7 @@ suite('ConfigurationEditingService', () => { instantiationService.stub(ITextFileService, disposables.add(instantiationService.createInstance(TestTextFileService))); instantiationService.stub(ITextModelService, disposables.add(instantiationService.createInstance(TextModelResolverService))); instantiationService.stub(ICommandService, CommandService); + instantiationService.stub(IUserConfigurationFileService, new UserConfigurationFileService(environmentService, fileService, logService)); testObject = instantiationService.createInstance(ConfigurationEditingService); }); @@ -155,11 +157,6 @@ suite('ConfigurationEditingService', () => { } }); - test('dirty error is not thrown if not asked to save', async () => { - instantiationService.stub(ITextFileService, 'isDirty', true); - await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotSave: true }); - }); - test('do not notify error', async () => { instantiationService.stub(ITextFileService, 'isDirty', true); const target = sinon.stub(); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 129bf5d3c7f83..b246c744a34d4 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -45,6 +45,7 @@ import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/enviro import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { hash } from 'vs/base/common/hash'; +import { IUserConfigurationFileService, UserConfigurationFileService } from 'vs/platform/configuration/common/userConfigurationFileService'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -693,6 +694,7 @@ suite('WorkspaceConfigurationService - Folder', () => { instantiationService.stub(IKeybindingEditingService, instantiationService.createInstance(KeybindingsEditingService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); + instantiationService.stub(IUserConfigurationFileService, new UserConfigurationFileService(environmentService, fileService, logService)); workspaceService.acquireInstantiationService(instantiationService); }); @@ -1321,6 +1323,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { instantiationService.stub(IKeybindingEditingService, instantiationService.createInstance(KeybindingsEditingService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); + instantiationService.stub(IUserConfigurationFileService, new UserConfigurationFileService(environmentService, fileService, logService)); workspaceService.acquireInstantiationService(instantiationService); workspaceContextService = workspaceService; @@ -1947,6 +1950,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { instantiationService.stub(IConfigurationService, testObject); instantiationService.stub(IEnvironmentService, environmentService); instantiationService.stub(IFileService, fileService); + instantiationService.stub(IUserConfigurationFileService, new UserConfigurationFileService(environmentService, fileService, logService)); }); async function initialize(): Promise { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 6ef428623e163..80c7f3d7e40ec 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -124,7 +124,9 @@ import { OpenerService } from 'vs/editor/browser/services/openerService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; +import { IUserConfigurationFileService, UserConfigurationFileService } from 'vs/platform/configuration/common/userConfigurationFileService'; +registerSingleton(IUserConfigurationFileService, UserConfigurationFileService); registerSingleton(IIgnoredExtensionsManagementService, IgnoredExtensionsManagementService); registerSingleton(IGlobalExtensionEnablementService, GlobalExtensionEnablementService); registerSingleton(IExtensionsStorageSyncService, ExtensionsStorageSyncService);