Skip to content

Commit

Permalink
enable workspace-scoped tasks
Browse files Browse the repository at this point in the history
Signed-off-by: Colin Grant <colin.grant@ericsson.com>
  • Loading branch information
Colin Grant committed Mar 2, 2021
1 parent cb561d3 commit 7e9bf9a
Show file tree
Hide file tree
Showing 23 changed files with 538 additions and 188 deletions.
112 changes: 112 additions & 0 deletions examples/api-tests/src/task-configurations.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/********************************************************************************
* Copyright (C) 2021 Ericsson and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

// @ts-check

describe('The Task Configuration Manager', function () {
this.timeout(5000);

const { assert } = chai;

const { WorkspaceService } = require('@theia/workspace/lib/browser/workspace-service');
const { TaskScope, TaskConfigurationScope } = require('@theia/task/lib/common/task-protocol');
const { TaskConfigurationManager } = require('@theia/task/lib/browser/task-configuration-manager');
const container = window.theia.container;
const workspaceService = container.get(WorkspaceService);
const taskConfigurationManager = container.get(TaskConfigurationManager);

const baseWorkspaceURI = workspaceService.tryGetRoots()[0].resource;
const baseWorkspaceRoot = baseWorkspaceURI.toString();

const basicTaskConfig = {
label: 'task',
type: 'shell',
command: 'top',
};

/** @type {Set<TaskConfigurationScope>} */
const scopesToClear = new Set();

describe('in a single-root workspace', () => {
beforeEach(() => clearTasks());
after(() => clearTasks());

setAndRetrieveTasks(() => TaskScope.Global, 'user');
setAndRetrieveTasks(() => TaskScope.Workspace, 'workspace');
setAndRetrieveTasks(() => baseWorkspaceRoot, 'folder');
});

async function clearTasks() {
await Promise.all(Array.from(scopesToClear, async scope => {
if (!!scope || scope === 0) {
await taskConfigurationManager.setTaskConfigurations(scope, []);
}
}));
scopesToClear.clear();
}

/**
* @param {() => TaskConfigurationScope} scopeGenerator a function to allow lazy evaluation of the second workspace root.
* @param {string} scopeLabel
* @param {boolean} only
*/
function setAndRetrieveTasks(scopeGenerator, scopeLabel, only = false) {
const testFunction = only ? it.only : it;
testFunction(`successfully handles ${scopeLabel} scope`, async () => {
const scope = scopeGenerator();
scopesToClear.add(scope);
const initialTasks = taskConfigurationManager.getTasks(scope);
assert.deepEqual(initialTasks, []);
await taskConfigurationManager.setTaskConfigurations(scope, [basicTaskConfig]);
const newTasks = taskConfigurationManager.getTasks(scope);
assert.deepEqual(newTasks, [basicTaskConfig]);
});
}

/* UNCOMMENT TO RUN MULTI-ROOT TESTS */
// const { FileService } = require('@theia/filesystem/lib/browser/file-service');
// const { EnvVariablesServer } = require('@theia/core/lib/common/env-variables');
// const URI = require('@theia/core/lib/common/uri').default;

// const fileService = container.get(FileService);
// /** @type {EnvVariablesServer} */
// const envVariables = container.get(EnvVariablesServer);

// describe('in a multi-root workspace', () => {
// let secondWorkspaceRoot = '';
// before(async () => {
// const configLocation = await envVariables.getConfigDirUri();
// const secondWorkspaceRootURI = new URI(configLocation).parent.resolve(`test-root-${Date.now()}`);
// secondWorkspaceRoot = secondWorkspaceRootURI.toString();
// await fileService.createFolder(secondWorkspaceRootURI);
// /** @type {Promise<void>} */
// const waitForEvent = new Promise(resolve => {
// const listener = taskConfigurationManager.onDidChangeTaskConfig(() => {
// listener.dispose();
// resolve();
// });
// });
// workspaceService.addRoot(secondWorkspaceRootURI);
// return waitForEvent;
// });
// beforeEach(() => clearTasks());
// after(() => clearTasks());
// setAndRetrieveTasks(() => TaskScope.Global, 'user');
// setAndRetrieveTasks(() => TaskScope.Workspace, 'workspace');
// setAndRetrieveTasks(() => baseWorkspaceRoot, 'folder (1)');
// setAndRetrieveTasks(() => secondWorkspaceRoot, 'folder (2)');
// });
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class PreferenceConfigurations {
return this.getSectionNames().indexOf(name) !== -1;
}

isAnyConfig(name: string): boolean {
return [...this.getSectionNames(), this.getConfigName()].includes(name);
}

isSectionUri(configUri: URI | undefined): boolean {
return !!configUri && this.isSectionName(this.getName(configUri));
}
Expand Down
24 changes: 9 additions & 15 deletions packages/core/src/browser/preferences/preference-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,30 +390,24 @@ export class PreferenceSchemaProvider extends PreferenceProvider {
return PreferenceSchemaProperties.is(value) && OVERRIDE_PROPERTY_PATTERN.test(name);
}

private updateSchemaProps(key: string, property: PreferenceDataProperty): void {
protected updateSchemaProps(key: string, property: PreferenceDataProperty): void {
this.combinedSchema.properties[key] = property;

switch (property.scope) {
case PreferenceScope.Workspace:
this.workspaceSchema.properties[key] = property;
break;
case PreferenceScope.Folder:
this.folderSchema.properties[key] = property;
// Fall through. isValidInScope implies that User ⊃ Workspace ⊃ Folder,
// so anything we add to folder should be added to workspace, but not vice versa.
case PreferenceScope.Workspace:
this.workspaceSchema.properties[key] = property;
break;
}
}

private removePropFromSchemas(key: string): void {
const scope = this.combinedSchema.properties[key].scope;

protected removePropFromSchemas(key: string): void {
// If we remove a key from combined, it should also be removed from all narrower scopes.
delete this.combinedSchema.properties[key];
switch (scope) {
case PreferenceScope.Workspace:
delete this.workspaceSchema.properties[key];
break;
case PreferenceScope.Folder:
delete this.folderSchema.properties[key];
break;
}
delete this.workspaceSchema.properties[key];
delete this.folderSchema.properties[key];
}
}
5 changes: 3 additions & 2 deletions packages/core/src/browser/preferences/preference-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,11 @@ export abstract class PreferenceProvider implements Disposable {
/**
* Retrieve the configuration URI for the given resource URI.
* @param resourceUri the uri of the resource or `undefined`.
* @param sectionName the section to return the URI for, e.g. `tasks` or `launch`. Defaults to settings.
*
* @returns the corresponding resource URI or `undefined` if there is no valid URI.
*/
getConfigUri(resourceUri?: string): URI | undefined {
getConfigUri(resourceUri?: string, sectionName?: string): URI | undefined {
return undefined;
}

Expand All @@ -205,7 +206,7 @@ export abstract class PreferenceProvider implements Disposable {
* @returns the first valid configuration URI contained by the given resource `undefined`
* if there is no valid configuration URI at all.
*/
getContainingConfigUri?(resourceUri?: string): URI | undefined;
getContainingConfigUri?(resourceUri?: string, sectionName?: string): URI | undefined;

static merge(source: JSONValue | undefined, target: JSONValue): JSONValue {
if (source === undefined || !JSONExt.isObject(source)) {
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/browser/preferences/preference-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,12 @@ export interface PreferenceService extends Disposable {
*
* @param scope the PreferenceScope to query for.
* @param resourceUri the optional uri of the resource-specific preference handling
* @param sectionName the optional preference section to query for.
*
* @returns the uri of the configuration resource for the given scope and optional resource uri it it exists,
* `undefined` otherwise.
*/
getConfigUri(scope: PreferenceScope, resourceUri?: string): URI | undefined;
getConfigUri(scope: PreferenceScope, resourceUri?: string, sectionName?: string): URI | undefined;
}

/**
Expand Down Expand Up @@ -512,16 +513,15 @@ export class PreferenceServiceImpl implements PreferenceService {
};
}

getConfigUri(scope: PreferenceScope, resourceUri?: string): URI | undefined {
getConfigUri(scope: PreferenceScope, resourceUri?: string, sectionName: string = this.configurations.getConfigName()): URI | undefined {
const provider = this.getProvider(scope);
if (!provider) {
if (!provider || !this.configurations.isAnyConfig(sectionName)) {
return undefined;
}
const configUri = provider.getConfigUri(resourceUri);
const configUri = provider.getConfigUri(resourceUri, sectionName);
if (configUri) {
return configUri;
}
return provider.getContainingConfigUri && provider.getContainingConfigUri(resourceUri);
return provider.getContainingConfigUri && provider.getContainingConfigUri(resourceUri, sectionName);
}

}
3 changes: 3 additions & 0 deletions packages/debug/src/browser/debug-schema-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import URI from '@theia/core/lib/common/uri';
import { DebugService } from '../common/debug-service';
import { debugPreferencesSchema } from './debug-preferences';
import { inputsSchema } from '@theia/variable-resolver/lib/browser/variable-input-schema';
import { WorkspaceService } from '@theia/workspace/lib/browser';

@injectable()
export class DebugSchemaUpdater implements JsonSchemaContribution {

protected readonly uri = new URI(launchSchemaId);

@inject(InMemoryResources) protected readonly inmemoryResources: InMemoryResources;
@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService;
@inject(DebugService) protected readonly debug: DebugService;

@postConstruct()
Expand All @@ -41,6 +43,7 @@ export class DebugSchemaUpdater implements JsonSchemaContribution {
fileMatch: ['launch.json'],
url: this.uri.toString()
});
this.workspaceService.updateSchema('launch', { $ref: this.uri.toString() });
}

async update(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,20 @@ export class FoldersPreferencesProvider extends PreferenceProvider {
}
}

getConfigUri(resourceUri?: string): URI | undefined {
getConfigUri(resourceUri?: string, sectionName: string = this.configurations.getConfigName()): URI | undefined {
for (const provider of this.getFolderProviders(resourceUri)) {
const configUri = provider.getConfigUri(resourceUri);
if (this.configurations.isConfigUri(configUri)) {
if (configUri && this.configurations.getName(configUri) === sectionName) {
return configUri;
}
}
return undefined;
}

getContainingConfigUri(resourceUri?: string): URI | undefined {
getContainingConfigUri(resourceUri?: string, sectionName: string = this.configurations.getConfigName()): URI | undefined {
for (const provider of this.getFolderProviders(resourceUri)) {
const configUri = provider.getConfigUri();
if (this.configurations.isConfigUri(configUri) && provider.contains(resourceUri)) {
if (provider.contains(resourceUri) && this.configurations.getName(configUri) === sectionName) {
return configUri;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { JsonSchemaRegisterContext, JsonSchemaContribution } from '@theia/core/l
import { PreferenceSchemaProvider } from '@theia/core/lib/browser/preferences/preference-contribution';
import { PreferenceConfigurations } from '@theia/core/lib/browser/preferences/preference-configurations';
import { PreferenceScope } from '@theia/core/lib/browser';
import { WorkspaceService } from '@theia/workspace/lib/browser';

const PREFERENCE_URI_PREFIX = 'vscode://schemas/settings/';
const USER_STORAGE_PREFIX = 'user-storage:/';
Expand All @@ -38,12 +39,16 @@ export class PreferencesJsonSchemaContribution implements JsonSchemaContribution
@inject(PreferenceConfigurations)
protected readonly preferenceConfigurations: PreferenceConfigurations;

@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;

registerSchemas(context: JsonSchemaRegisterContext): void {
this.registerSchema(PreferenceScope.Default, context);
this.registerSchema(PreferenceScope.User, context);
this.registerSchema(PreferenceScope.Workspace, context);
this.registerSchema(PreferenceScope.Folder, context);

this.workspaceService.updateSchema('settings', { $ref: this.getSchemaURIForScope(PreferenceScope.Workspace).toString() });
this.schemaProvider.onDidPreferenceSchemaChanged(() => this.updateInMemoryResources());
}

Expand All @@ -60,16 +65,20 @@ export class PreferencesJsonSchemaContribution implements JsonSchemaContribution
}

private updateInMemoryResources(): void {
this.inmemoryResources.update(new URI(PREFERENCE_URI_PREFIX + PreferenceScope[PreferenceScope.Default].toLowerCase()),
this.inmemoryResources.update(this.getSchemaURIForScope(PreferenceScope.Default),
this.serializeSchema(+PreferenceScope.Default));
this.inmemoryResources.update(new URI(PREFERENCE_URI_PREFIX + PreferenceScope[PreferenceScope.User].toLowerCase()),
this.inmemoryResources.update(this.getSchemaURIForScope(PreferenceScope.User),
this.serializeSchema(+PreferenceScope.User));
this.inmemoryResources.update(new URI(PREFERENCE_URI_PREFIX + PreferenceScope[PreferenceScope.Workspace].toLowerCase()),
this.inmemoryResources.update(this.getSchemaURIForScope(PreferenceScope.Workspace),
this.serializeSchema(+PreferenceScope.Workspace));
this.inmemoryResources.update(new URI(PREFERENCE_URI_PREFIX + PreferenceScope[PreferenceScope.Folder].toLowerCase()),
this.inmemoryResources.update(this.getSchemaURIForScope(PreferenceScope.Folder),
this.serializeSchema(+PreferenceScope.Folder));
}

private getSchemaURIForScope(scope: PreferenceScope): URI {
return new URI(PREFERENCE_URI_PREFIX + PreferenceScope[scope].toLowerCase());
}

private getFileMatch(scope: string): string[] {
const baseName = this.preferenceConfigurations.getConfigName() + '.json';
return [baseName, new URI(USER_STORAGE_PREFIX + scope).resolve(baseName).toString()];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export abstract class SectionPreferenceProvider extends AbstractResourcePreferen
if (preferenceName === this.section) {
return [];
}
if (preferenceName.startsWith(this.section + '.')) {
return [preferenceName.substr(this.section!.length + 1)];
if (preferenceName.startsWith(`${this.section}.`)) {
return [preferenceName.slice(this.section.length + 1)];
}
return undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ export class UserConfigsPreferenceProvider extends PreferenceProvider {
}
}

getConfigUri(resourceUri?: string): URI | undefined {
getConfigUri(resourceUri?: string, sectionName: string = this.configurations.getConfigName()): URI | undefined {
for (const provider of this.providers.values()) {
const configUri = provider.getConfigUri(resourceUri);
if (this.configurations.isConfigUri(configUri)) {
if (configUri && this.configurations.getName(configUri) === sectionName) {
return configUri;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export class WorkspaceFilePreferenceProvider extends AbstractResourcePreferenceP
@inject(WorkspaceFilePreferenceProviderOptions)
protected readonly options: WorkspaceFilePreferenceProviderOptions;

protected sectionsInsideSettings = new Set<string>();

protected getUri(): URI {
return this.options.workspaceUri;
}
Expand All @@ -45,12 +47,38 @@ export class WorkspaceFilePreferenceProvider extends AbstractResourcePreferenceP
protected parse(content: string): any {
const data = super.parse(content);
if (WorkspaceData.is(data)) {
return data.settings || {};
const settings = { ...data.settings };
for (const key of this.configurations.getSectionNames().filter(name => name !== 'settings')) {
// If the user has written configuration inside the "settings" object, we will respect that.
if (settings[key]) {
this.sectionsInsideSettings.add(key);
}
// Favor sections outside the "settings" object to agree with VSCode behavior
if (data[key]) {
settings[key] = data[key];
this.sectionsInsideSettings.delete(key);
}
}
return settings;
}
return {};
}

protected getPath(preferenceName: string): string[] {
const firstSegment = preferenceName.split('.')[0];
if (firstSegment && this.configurations.isSectionName(firstSegment)) {
// Default to writing sections outside the "settings" object.
const path = [firstSegment];
const pathRemainder = preferenceName.slice(firstSegment.length + 1);
if (pathRemainder) {
path.push(pathRemainder);
}
// If the user has already written this section inside the "settings" object, modify it there.
if (this.sectionsInsideSettings.has(firstSegment)) {
path.unshift('settings');
}
return path;
}
return ['settings', preferenceName];
}

Expand Down
Loading

0 comments on commit 7e9bf9a

Please sign in to comment.