#125970 move user configuration writes into separate service
This commit is contained in:
parent
47bccfa1c8
commit
155de9d895
|
@ -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
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue