#125970 move user configuration writes into separate service

This commit is contained in:
Sandeep Somavarapu 2021-06-14 22:47:22 +02:00
parent 47bccfa1c8
commit 155de9d895
No known key found for this signature in database
GPG key ID: 1FED25EC4646638B
5 changed files with 172 additions and 80 deletions

View file

@ -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<IUserConfigurationFileService>(UserConfigurationFileServiceId);
export interface IUserConfigurationFileService {
readonly _serviceBrand: undefined;
updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise<void>;
}
export class UserConfigurationFileService implements IUserConfigurationFileService {
readonly _serviceBrand: undefined;
private readonly queue: Queue<void>;
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IFileService private readonly fileService: IFileService,
@ILogService private readonly logService: ILogService,
) {
this.queue = new Queue<void>();
}
async updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise<void> {
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<void> {
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 ((<FileOperationError>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
}];
}
}

View file

@ -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<void>();
remoteAgentService.getEnvironment().then(environment => {
@ -179,15 +176,22 @@ export class ConfigurationEditingService {
}
private async doWriteConfiguration(operation: IConfigurationEditOperation, options: ConfigurationEditingOptions): Promise<void> {
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 ((<FileOperationError>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>error).message === UserConfigurationErrorCode.ERROR_INVALID_FILE) {
throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, operation.target, operation);
}
if ((<Error>error).message === UserConfigurationErrorCode.ERROR_FILE_MODIFIED_SINCE || (<FileOperationError>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<any> {
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<any> {
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<void> {
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 }, <ConfigurationEditingOptions>{ force: true, scopes });
this.writeConfiguration(operation.target, { key, value: operation.value }, <ConfigurationEditingOptions>{ 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 }, <ConfigurationEditingOptions>{ force: true, scopes })
run: () => this.writeConfiguration(operation.target, { key: operation.key, value: operation.value }, <ConfigurationEditingOptions>{ 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<T = never>(code: ConfigurationEditingErrorCode, target: EditableConfigurationTarget, operation: IConfigurationEditOperation): Promise<T> {
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<IReference<IResolvedTextEditorModel>> {
private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationOverrides): Promise<void> {
// 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<IConfigurationRegistry>(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<IConfigurationRegistry>(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<IConfigurationRegistry>(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 (checkDirty && this.textFileService.isDirty(operation.resource)) {
throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation);
}
if (this.hasParseErrors(model, operation)) {
reference.dispose();
return this.reject<typeof reference>(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, 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<typeof reference>(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation);
}
return reference;
});
}
private getConfigurationEditOperation(target: EditableConfigurationTarget, config: IConfigurationValue, overrides: IConfigurationOverrides): IConfigurationEditOperation {

View file

@ -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, <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();

View file

@ -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, <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, <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<void> {

View file

@ -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);