Add tasks to user configuration (#81496)

Part of #1435
This commit is contained in:
Alex Ross 2019-12-17 15:39:57 +01:00 committed by GitHub
parent 16125bcbe3
commit f837c3e54f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 292 additions and 176 deletions

View file

@ -88,6 +88,10 @@
"fileMatch": "/.vscode/tasks.json",
"url": "vscode://schemas/tasks"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/tasks.json",
"url": "vscode://schemas/tasks"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/snippets/*.json",
"url": "vscode://schemas/snippets"

View file

@ -7,12 +7,12 @@ import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
import { Event, Emitter } from 'vs/base/common/event';
import * as errors from 'vs/base/common/errors';
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files';
import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels';
import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@ -26,28 +26,61 @@ import { hash } from 'vs/base/common/hash';
export class UserConfiguration extends Disposable {
private readonly parser: ConfigurationModelParser;
private readonly reloadConfigurationScheduler: RunOnceScheduler;
protected readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
private readonly _onDidInitializeCompleteConfiguration: Emitter<void> = this._register(new Emitter<void>());
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
private readonly userConfiguration: MutableDisposable<UserSettings | FileServiceBasedConfigurationWithNames> = this._register(new MutableDisposable<UserSettings | FileServiceBasedConfigurationWithNames>());
private readonly reloadConfigurationScheduler: RunOnceScheduler;
constructor(
private readonly userSettingsResource: URI,
private readonly scopes: ConfigurationScope[] | undefined,
private readonly fileService: IFileService
) {
super();
this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes);
this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, this.fileService);
this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule()));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this.reloadConfigurationScheduler.schedule()));
runWhenIdle(() => this._onDidInitializeCompleteConfiguration.fire(), 5000);
this._register(Event.once(this._onDidInitializeCompleteConfiguration.event)(() => this.reloadConfigurationScheduler.schedule()));
}
async initialize(): Promise<ConfigurationModel> {
return this.reload();
return this.userConfiguration.value!.loadConfiguration();
}
async reload(): Promise<ConfigurationModel> {
if (!(this.userConfiguration.value instanceof FileServiceBasedConfigurationWithNames)) {
this.userConfiguration.value = new FileServiceBasedConfigurationWithNames(resources.dirname(this.userSettingsResource), [FOLDER_SETTINGS_NAME, TASKS_CONFIGURATION_KEY], this.scopes, this.fileService);
this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule()));
}
return this.userConfiguration.value!.loadConfiguration();
}
reprocess(): ConfigurationModel {
return this.userConfiguration.value!.reprocess();
}
}
class UserSettings extends Disposable {
private readonly parser: ConfigurationModelParser;
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(
private readonly userSettingsResource: URI,
private readonly scopes: ConfigurationScope[] | undefined,
private readonly fileService: IFileService
) {
super();
this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes);
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this._onDidChange.fire()));
}
async loadConfiguration(): Promise<ConfigurationModel> {
try {
const content = await this.fileService.readFile(this.userSettingsResource);
this.parser.parseContent(content.value.toString() || '{}');
@ -63,6 +96,127 @@ export class UserConfiguration extends Disposable {
}
}
class FileServiceBasedConfigurationWithNames extends Disposable {
private _folderSettingsModelParser: ConfigurationModelParser;
private _standAloneConfigurations: ConfigurationModel[];
private _cache: ConfigurationModel;
protected readonly configurationResources: URI[];
protected changeEventTriggerScheduler: RunOnceScheduler;
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(protected readonly configurationFolder: URI,
private readonly configurationNames: string[],
private readonly scopes: ConfigurationScope[] | undefined,
private fileService: IFileService) {
super();
this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`));
this._folderSettingsModelParser = new ConfigurationModelParser(this.configurationFolder.toString(), this.scopes);
this._standAloneConfigurations = [];
this._cache = new ConfigurationModel();
this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));
this._register(this.fileService.onFileChanges((e) => this.handleFileEvents(e)));
}
async loadConfiguration(): Promise<ConfigurationModel> {
const configurationContents = await Promise.all(this.configurationResources.map(async resource => {
try {
const content = await this.fileService.readFile(resource);
return content.value.toString();
} catch (error) {
const exists = await this.fileService.exists(resource);
if (exists) {
errors.onUnexpectedError(error);
}
}
return undefined;
}));
// reset
this._standAloneConfigurations = [];
this._folderSettingsModelParser.parseContent('');
// parse
if (configurationContents[0]) {
this._folderSettingsModelParser.parseContent(configurationContents[0]);
}
for (let index = 1; index < configurationContents.length; index++) {
const contents = configurationContents[index];
if (contents) {
const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]);
standAloneConfigurationModelParser.parseContent(contents);
this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);
}
}
// Consolidate (support *.json files in the workspace settings folder)
this.consolidate();
return this._cache;
}
reprocess(): ConfigurationModel {
const oldContents = this._folderSettingsModelParser.configurationModel.contents;
this._folderSettingsModelParser.parse();
if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) {
this.consolidate();
}
return this._cache;
}
private consolidate(): void {
this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations);
}
protected async handleFileEvents(event: FileChangesEvent): Promise<void> {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect workspace configuration files
for (let i = 0, len = events.length; i < len; i++) {
const resource = events[i].resource;
const basename = resources.basename(resource);
const isJson = extname(basename) === '.json';
const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder));
if (!isJson && !isConfigurationFolderDeleted) {
continue; // only JSON files or the actual settings folder
}
const folderRelativePath = this.toFolderRelativePath(resource);
if (!folderRelativePath) {
continue; // event is not inside folder
}
// Handle case where ".vscode" got deleted
if (isConfigurationFolderDeleted) {
affectedByChanges = true;
break;
}
// only valid workspace config files
if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) {
affectedByChanges = true;
break;
}
}
if (affectedByChanges) {
this.changeEventTriggerScheduler.schedule();
}
}
private toFolderRelativePath(resource: URI): string | undefined {
if (resources.isEqualOrParent(resource, this.configurationFolder)) {
return resources.relativePath(this.configurationFolder, resource);
}
return undefined;
}
}
export class RemoteUserConfiguration extends Disposable {
private readonly _cachedConfiguration: CachedRemoteUserConfiguration;
@ -546,125 +700,12 @@ export interface IFolderConfiguration extends IDisposable {
reprocess(): ConfigurationModel;
}
class FileServiceBasedFolderConfiguration extends Disposable implements IFolderConfiguration {
class FileServiceBasedFolderConfiguration extends FileServiceBasedConfigurationWithNames implements IFolderConfiguration {
private _folderSettingsModelParser: ConfigurationModelParser;
private _standAloneConfigurations: ConfigurationModel[];
private _cache: ConfigurationModel;
private readonly configurationNames: string[];
protected readonly configurationResources: URI[];
private changeEventTriggerScheduler: RunOnceScheduler;
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(protected readonly configurationFolder: URI, workbenchState: WorkbenchState, private fileService: IFileService) {
super();
this.configurationNames = [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY];
this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`));
this._folderSettingsModelParser = new ConfigurationModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES);
this._standAloneConfigurations = [];
this._cache = new ConfigurationModel();
this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50));
this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e)));
constructor(configurationFolder: URI, workbenchState: WorkbenchState, fileService: IFileService) {
super(configurationFolder, [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY], WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService);
}
async loadConfiguration(): Promise<ConfigurationModel> {
const configurationContents = await Promise.all(this.configurationResources.map(async resource => {
try {
const content = await this.fileService.readFile(resource);
return content.value.toString();
} catch (error) {
const exists = await this.fileService.exists(resource);
if (exists) {
errors.onUnexpectedError(error);
}
}
return undefined;
}));
// reset
this._standAloneConfigurations = [];
this._folderSettingsModelParser.parseContent('');
// parse
if (configurationContents[0]) {
this._folderSettingsModelParser.parseContent(configurationContents[0]);
}
for (let index = 1; index < configurationContents.length; index++) {
const contents = configurationContents[index];
if (contents) {
const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]);
standAloneConfigurationModelParser.parseContent(contents);
this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);
}
}
// Consolidate (support *.json files in the workspace settings folder)
this.consolidate();
return this._cache;
}
reprocess(): ConfigurationModel {
const oldContents = this._folderSettingsModelParser.configurationModel.contents;
this._folderSettingsModelParser.parse();
if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) {
this.consolidate();
}
return this._cache;
}
private consolidate(): void {
this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations);
}
private handleWorkspaceFileEvents(event: FileChangesEvent): void {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect workspace configuration files
for (let i = 0, len = events.length; i < len; i++) {
const resource = events[i].resource;
const basename = resources.basename(resource);
const isJson = extname(basename) === '.json';
const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder));
if (!isJson && !isConfigurationFolderDeleted) {
continue; // only JSON files or the actual settings folder
}
const folderRelativePath = this.toFolderRelativePath(resource);
if (!folderRelativePath) {
continue; // event is not inside folder
}
// Handle case where ".vscode" got deleted
if (isConfigurationFolderDeleted) {
affectedByChanges = true;
break;
}
// only valid workspace config files
if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) {
affectedByChanges = true;
break;
}
}
if (affectedByChanges) {
this.changeEventTriggerScheduler.schedule();
}
}
private toFolderRelativePath(resource: URI): string | undefined {
if (resources.isEqualOrParent(resource, this.configurationFolder)) {
return resources.relativePath(this.configurationFolder, resource);
}
return undefined;
}
}
class CachedFolderConfiguration extends Disposable implements IFolderConfiguration {

View file

@ -28,6 +28,8 @@ export const LAUNCH_CONFIGURATION_KEY = 'launch';
export const WORKSPACE_STANDALONE_CONFIGURATIONS = Object.create(null);
WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/${TASKS_CONFIGURATION_KEY}.json`;
WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/${LAUNCH_CONFIGURATION_KEY}.json`;
export const USER_STANDALONE_CONFIGURATIONS = Object.create(null);
USER_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${TASKS_CONFIGURATION_KEY}.json`;
export type ConfigurationKey = { type: 'user' | 'workspaces' | 'folder', key: string };

View file

@ -5,6 +5,7 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
import * as json from 'vs/base/common/json';
import * as strings from 'vs/base/common/strings';
import { setProperty } from 'vs/base/common/jsonEdit';
@ -19,7 +20,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/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 } from 'vs/workbench/services/configuration/common/configuration';
import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
@ -430,8 +431,8 @@ export class ConfigurationEditingService {
}
if (operation.workspaceStandAloneConfigurationKey) {
// Global tasks and launches are not supported
if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) {
// 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);
}
}
@ -497,9 +498,10 @@ export class ConfigurationEditingService {
// Check for standalone workspace configurations
if (config.key) {
const standaloneConfigurationKeys = Object.keys(WORKSPACE_STANDALONE_CONFIGURATIONS);
const standaloneConfigurationMap = target === EditableConfigurationTarget.USER_LOCAL ? USER_STANDALONE_CONFIGURATIONS : WORKSPACE_STANDALONE_CONFIGURATIONS;
const standaloneConfigurationKeys = Object.keys(standaloneConfigurationMap);
for (const key of standaloneConfigurationKeys) {
const resource = this.getConfigurationFileResource(target, config, WORKSPACE_STANDALONE_CONFIGURATIONS[key], overrides.resource);
const resource = this.getConfigurationFileResource(target, config, standaloneConfigurationMap[key], overrides.resource);
// Check for prefix
if (config.key === key) {
@ -536,7 +538,11 @@ export class ConfigurationEditingService {
private getConfigurationFileResource(target: EditableConfigurationTarget, config: IConfigurationValue, relativePath: string, resource: URI | null | undefined): URI | null {
if (target === EditableConfigurationTarget.USER_LOCAL) {
return this.environmentService.settingsResource;
if (relativePath) {
return resources.joinPath(resources.dirname(this.environmentService.settingsResource), relativePath);
} else {
return this.environmentService.settingsResource;
}
}
if (target === EditableConfigurationTarget.USER_REMOTE) {
return this.remoteSettingsResource;

View file

@ -18,7 +18,7 @@ import * as uuid from 'vs/base/common/uuid';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService';
import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH } from 'vs/workbench/services/configuration/common/configuration';
import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@ -60,6 +60,7 @@ suite('ConfigurationEditingService', () => {
let parentDir: string;
let workspaceDir: string;
let globalSettingsFile: string;
let globalTasksFile: string;
let workspaceSettingsDir;
suiteSetup(() => {
@ -94,6 +95,7 @@ suite('ConfigurationEditingService', () => {
parentDir = path.join(os.tmpdir(), 'vsctests', id);
workspaceDir = path.join(parentDir, 'workspaceconfig', id);
globalSettingsFile = path.join(workspaceDir, 'settings.json');
globalTasksFile = path.join(workspaceDir, 'tasks.json');
workspaceSettingsDir = path.join(workspaceDir, '.vscode');
return await mkdirp(workspaceSettingsDir, 493);
@ -149,12 +151,6 @@ suite('ConfigurationEditingService', () => {
(error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY));
});
test('errors cases - invalid target', () => {
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks.something', value: 'value' })
.then(() => assert.fail('Should fail with ERROR_INVALID_TARGET'),
(error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET));
});
test('errors cases - no workspace', () => {
return setUpServices(true)
.then(() => testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'configurationEditing.service.testSetting', value: 'value' }))
@ -162,11 +158,19 @@ suite('ConfigurationEditingService', () => {
(error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED));
});
test('errors cases - invalid configuration', () => {
fs.writeFileSync(globalSettingsFile, ',,,,,,,,,,,,,,');
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' })
function errorCasesInvalidConfig(file: string, key: string) {
fs.writeFileSync(file, ',,,,,,,,,,,,,,');
return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value: 'value' })
.then(() => assert.fail('Should fail with ERROR_INVALID_CONFIGURATION'),
(error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION));
}
test('errors cases - invalid configuration', () => {
return errorCasesInvalidConfig(globalSettingsFile, 'configurationEditing.service.testSetting');
});
test('errors cases - invalid global tasks configuration', () => {
return errorCasesInvalidConfig(globalTasksFile, 'tasks.configurationEditing.service.testSetting');
});
test('errors cases - dirty', () => {
@ -271,44 +275,89 @@ suite('ConfigurationEditingService', () => {
});
});
test('write workspace standalone setting - empty file', () => {
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' })
function writeStandaloneSettingEmptyFile(configTarget: EditableConfigurationTarget, pathMap: any) {
return testObject.writeConfiguration(configTarget, { key: 'tasks.service.testSetting', value: 'value' })
.then(() => {
const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
const target = path.join(workspaceDir, pathMap['tasks']);
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['service.testSetting'], 'value');
});
}
test('write workspace standalone setting - empty file', () => {
return writeStandaloneSettingEmptyFile(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write workspace standalone setting - existing file', () => {
const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['launch']);
test('write user standalone setting - empty file', () => {
return writeStandaloneSettingEmptyFile(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingExitingFile(configTarget: EditableConfigurationTarget, pathMap: any) {
const target = path.join(workspaceDir, pathMap['tasks']);
fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }');
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'launch.service.testSetting', value: 'value' })
return testObject.writeConfiguration(configTarget, { key: 'tasks.service.testSetting', value: 'value' })
.then(() => {
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['service.testSetting'], 'value');
assert.equal(parsed['my.super.setting'], 'my.super.value');
});
}
test('write workspace standalone setting - existing file', () => {
return writeStandaloneSettingExitingFile(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting - existing file', () => {
return writeStandaloneSettingExitingFile(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingEmptyFileFullJson(configTarget: EditableConfigurationTarget, pathMap: any) {
return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
.then(() => {
const target = path.join(workspaceDir, pathMap['tasks']);
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
}
test('write workspace standalone setting - empty file - full JSON', () => {
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
return writeStandaloneSettingEmptyFileFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting - empty file - full JSON', () => {
return writeStandaloneSettingEmptyFileFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingExistingFileFullJson(configTarget: EditableConfigurationTarget, pathMap: any) {
const target = path.join(workspaceDir, pathMap['tasks']);
fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }');
return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
.then(() => {
const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
});
}
test('write workspace standalone setting - existing file - full JSON', () => {
const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }');
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
return writeStandaloneSettingExistingFileFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting - existing file - full JSON', () => {
return writeStandaloneSettingExistingFileFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingExistingFileWithJsonErrorFullJson(configTarget: EditableConfigurationTarget, pathMap: any) {
const target = path.join(workspaceDir, pathMap['tasks']);
fs.writeFileSync(target, '{ "my.super.setting": '); // invalid JSON
return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
.then(() => {
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
@ -316,23 +365,18 @@ suite('ConfigurationEditingService', () => {
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
});
}
test('write workspace standalone setting - existing file with JSON errors - full JSON', () => {
const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
fs.writeFileSync(target, '{ "my.super.setting": '); // invalid JSON
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } })
.then(() => {
const contents = fs.readFileSync(target).toString('utf8');
const parsed = json.parse(contents);
assert.equal(parsed['version'], '1.0.0');
assert.equal(parsed['tasks'][0]['taskName'], 'myTask');
});
return writeStandaloneSettingExistingFileWithJsonErrorFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write workspace standalone setting should replace complete file', () => {
const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']);
test('write user standalone setting - existing file with JSON errors - full JSON', () => {
return writeStandaloneSettingExistingFileWithJsonErrorFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
function writeStandaloneSettingShouldReplace(configTarget: EditableConfigurationTarget, pathMap: any) {
const target = path.join(workspaceDir, pathMap['tasks']);
fs.writeFileSync(target, `{
"version": "1.0.0",
"tasks": [
@ -344,11 +388,19 @@ suite('ConfigurationEditingService', () => {
}
]
}`);
return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } })
return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } })
.then(() => {
const actual = fs.readFileSync(target).toString('utf8');
const expected = JSON.stringify({ 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] }, null, '\t');
assert.equal(actual, expected);
});
}
test('write workspace standalone setting should replace complete file', () => {
return writeStandaloneSettingShouldReplace(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS);
});
test('write user standalone setting should replace complete file', () => {
return writeStandaloneSettingShouldReplace(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS);
});
});

View file

@ -47,6 +47,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file
import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { timeout } from 'vs/base/common/async';
class TestEnvironmentService extends NativeWorkbenchEnvironmentService {
@ -717,7 +718,7 @@ suite('WorkspaceService - Initialization', () => {
suite('WorkspaceConfigurationService - Folder', () => {
let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string;
let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string;
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
suiteSetup(() => {
@ -756,6 +757,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
parentResource = parentDir;
workspaceDir = folderDir;
globalSettingsFile = path.join(parentDir, 'settings.json');
globalTasksFile = path.join(parentDir, 'tasks.json');
const instantiationService = <TestInstantiationService>workbenchInstantiationService();
const environmentService = new TestEnvironmentService(URI.file(parentDir));
@ -1074,6 +1076,17 @@ suite('WorkspaceConfigurationService - Folder', () => {
.then(() => assert.ok(target.called));
});
test('no change event when there are no global tasks', async () => {
const target = sinon.spy();
testObject.onDidChangeConfiguration(target);
await timeout(500);
assert.ok(target.notCalled);
});
test('change event when there are global tasks', () => {
fs.writeFileSync(globalTasksFile, '{ "version": "1.0.0", "tasks": [{ "taskName": "myTask" }');
return new Promise((c) => testObject.onDidChangeConfiguration(() => c()));
});
});
suite('WorkspaceConfigurationService-Multiroot', () => {
@ -1430,7 +1443,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
});
});
test('inspect tasks configuration', () => {
test('inspect tasks configuration', async () => {
const expectedTasksConfiguration = {
'version': '2.0.0',
'tasks': [
@ -1445,12 +1458,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
}
]
};
return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: expectedTasksConfiguration }], true)
.then(() => testObject.reloadConfiguration())
.then(() => {
const actual = testObject.inspect('tasks').workspaceValue;
assert.deepEqual(actual, expectedTasksConfiguration);
});
await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: expectedTasksConfiguration }], true);
await testObject.reloadConfiguration();
const actual = testObject.inspect('tasks').workspaceValue;
assert.deepEqual(actual, expectedTasksConfiguration);
});
test('update user configuration', () => {