Skip to content

Commit

Permalink
support "group" in the task config
Browse files Browse the repository at this point in the history
- With this change users would be able to define and run build tasks,
test tasks, default build tasks, and default test tasks.
- resolves #6144

Signed-off-by: Liang Huang <[email protected]>
  • Loading branch information
Liang Huang committed Nov 11, 2019
1 parent 2f3b2c3 commit 94e2122
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 24 deletions.
109 changes: 104 additions & 5 deletions packages/task/src/browser/quick-open-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { inject, injectable } from 'inversify';
import { TaskService } from './task-service';
import { TaskInfo, TaskConfiguration } from '../common/task-protocol';
import { TaskInfo, TaskConfiguration, TaskCustomization } from '../common/task-protocol';
import { TaskDefinitionRegistry } from './task-definition-registry';
import URI from '@theia/core/lib/common/uri';
import { TaskActionProvider } from './task-action-provider';
Expand Down Expand Up @@ -259,6 +259,78 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler {
});
}

async runBuildOrTestTask(buildOrTestType: 'build' | 'test'): Promise<void> {
const shouldRunBuildTask = buildOrTestType === 'build';
await this.init();
if (this.items.length > 1 ||
this.items.length === 1 && (this.items[0] as TaskRunQuickOpenItem).getTask !== undefined) { // the item in `this.items` is not 'No tasks found'

const buildOrTestTasks = this.items.filter((t: TaskRunQuickOpenItem) =>
shouldRunBuildTask ? TaskCustomization.isBuildTask(t.getTask()) : TaskCustomization.isTestTask(t.getTask())
);
this.actionProvider = undefined;
if (buildOrTestTasks.length > 0) { // build / test tasks are defined in the workspace
const defaultBuildOrTestTasks = buildOrTestTasks.filter((t: TaskRunQuickOpenItem) =>
shouldRunBuildTask ? TaskCustomization.isDefaultBuildTask(t.getTask()) : TaskCustomization.isDefaultTestTask(t.getTask())
);
if (defaultBuildOrTestTasks.length === 1) { // run the default build / test task
const defaultBuildOrTestTask = defaultBuildOrTestTasks[0];
const taskToRun = (defaultBuildOrTestTask as TaskRunQuickOpenItem).getTask();
if (this.taskDefinitionRegistry && !!this.taskDefinitionRegistry.getDefinition(taskToRun)) {
this.taskService.run(taskToRun.source, taskToRun.label);
} else {
this.taskService.run(taskToRun._source, taskToRun.label);
}
return;
}

// if default build / test task is not found, or there are more than one default,
// display the list of build /test tasks to let the user decide which to run
this.items = buildOrTestTasks;

} else { // no build / test tasks, display an action item to configure the build / test task
this.items = [new QuickOpenItem({
label: `No ${buildOrTestType} task to run found. Configure ${buildOrTestType} task...`,
run: (mode: QuickOpenMode): boolean => {
if (mode !== QuickOpenMode.OPEN) {
return false;
}

this.init().then(() => {
// update the `tasks.json` file, instead of running the task itself
this.items = this.items.map((item: TaskRunQuickOpenItem) => {
const newItem = new ConfigureBuildOrTestTaskQuickOpenItem(
item.getTask(),
this.taskService,
this.workspaceService.isMultiRootWorkspaceOpened,
item.options,
this.taskNameResolver,
shouldRunBuildTask,
this.taskConfigurationManager
);
newItem['taskDefinitionRegistry'] = this.taskDefinitionRegistry;
return newItem;
});
this.quickOpenService.open(this, {
placeholder: `Select the task to be used as the default ${buildOrTestType} task`,
fuzzyMatchLabel: true,
fuzzySort: false
});
});

return true;
}
})];
}
}

this.quickOpenService.open(this, {
placeholder: `Select the ${buildOrTestType} task to run`,
fuzzyMatchLabel: true,
fuzzySort: false
});
}

onType(lookFor: string, acceptor: (items: QuickOpenItem[], actionProvider?: QuickOpenActionProvider) => void): void {
acceptor(this.items, this.actionProvider);
}
Expand All @@ -272,9 +344,9 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler {

const filteredRecentTasks: TaskConfiguration[] = [];
recentTasks.forEach(recent => {
const exist = [...configuredTasks, ...providedTasks].some(t => this.taskDefinitionRegistry.compareTasks(recent, t));
if (exist) {
filteredRecentTasks.push(recent);
const originalTaskConfig = [...configuredTasks, ...providedTasks].find(t => this.taskDefinitionRegistry.compareTasks(recent, t));
if (originalTaskConfig) {
filteredRecentTasks.push(originalTaskConfig);
}
});

Expand Down Expand Up @@ -324,7 +396,7 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem {
protected readonly task: TaskConfiguration,
protected taskService: TaskService,
protected isMulti: boolean,
protected readonly options: QuickOpenGroupItemOptions,
public readonly options: QuickOpenGroupItemOptions,
protected readonly taskNameResolver: TaskNameResolver,
) {
super(options);
Expand Down Expand Up @@ -371,6 +443,33 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem {
}
}

export class ConfigureBuildOrTestTaskQuickOpenItem extends TaskRunQuickOpenItem {
constructor(
protected readonly task: TaskConfiguration,
protected taskService: TaskService,
protected isMulti: boolean,
public readonly options: QuickOpenGroupItemOptions,
protected readonly taskNameResolver: TaskNameResolver,
protected readonly isBuildTask: boolean,
protected taskConfigurationManager: TaskConfigurationManager
) {
super(task, taskService, isMulti, options, taskNameResolver);
}

run(mode: QuickOpenMode): boolean {
if (mode !== QuickOpenMode.OPEN) {
return false;
}
this.taskService.updateTaskConfiguration(this.task, { group: { kind: this.isBuildTask ? 'build' : 'test', isDefault: true } })
.then(() => {
if (this.task._scope) {
this.taskConfigurationManager.openConfiguration(this.task._scope);
}
});
return true;
}
}

export class TaskAttachQuickOpenItem extends QuickOpenItem {

constructor(
Expand Down
36 changes: 21 additions & 15 deletions packages/task/src/browser/task-configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export class TaskConfigurations implements Disposable {
for (const cus of customizations) {
const detected = await this.providedTaskConfigurations.getTaskToCustomize(cus, rootFolder);
if (detected) {
detectedTasksAsConfigured.push(detected);
detectedTasksAsConfigured.push({ ...detected, ...cus });
}
}
}
Expand Down Expand Up @@ -280,7 +280,7 @@ export class TaskConfigurations implements Disposable {

const configuredAndCustomizedTasks = await this.getTasks();
if (!configuredAndCustomizedTasks.some(t => this.taskDefinitionRegistry.compareTasks(t, task))) {
await this.saveTask(sourceFolderUri, task);
await this.saveTask(sourceFolderUri, { ...task, problemMatcher: [] });
}

try {
Expand All @@ -302,8 +302,8 @@ export class TaskConfigurations implements Disposable {
customization[p] = task[p];
}
});
const problemMatcher: string[] = [];
if (task.problemMatcher) {
if ('problemMatcher' in task) {
const problemMatcher: string[] = [];
if (Array.isArray(task.problemMatcher)) {
problemMatcher.push(...task.problemMatcher.map(t => {
if (typeof t === 'string') {
Expand All @@ -314,14 +314,15 @@ export class TaskConfigurations implements Disposable {
}));
} else if (typeof task.problemMatcher === 'string') {
problemMatcher.push(task.problemMatcher);
} else {
} else if (task.problemMatcher) {
problemMatcher.push(task.problemMatcher.name!);
}
customization.problemMatcher = problemMatcher.map(name => name.startsWith('$') ? name : `$${name}`);
}
return {
...customization,
problemMatcher: problemMatcher.map(name => name.startsWith('$') ? name : `$${name}`)
};
if (task.group) {
customization.group = task.group;
}
return { ...customization };
}

/** Writes the task to a config file. Creates a config file if this one does not exist */
Expand Down Expand Up @@ -370,11 +371,14 @@ export class TaskConfigurations implements Disposable {
}

/**
* saves the names of the problem matchers to be used to parse the output of the given task to `tasks.json`
* @param task task that the problem matcher(s) are applied to
* @param problemMatchers name(s) of the problem matcher(s)
* Updates the task config in the `tasks.json`.
* The task config, together with updates, will be written into the `tasks.json` if it is not found in the file.
*
* @param task task that the updates will be applied to
* @param update the updates to be appplied
*/
async saveProblemMatcherForTask(task: TaskConfiguration, problemMatchers: string[]): Promise<void> {
// tslint:disable-next-line:no-any
async updateTaskConfig(task: TaskConfiguration, update: { [name: string]: any }): Promise<void> {
const sourceFolderUri: string | undefined = this.getSourceFolderUriFromTask(task);
if (!sourceFolderUri) {
console.error('Global task cannot be customized');
Expand All @@ -396,12 +400,14 @@ export class TaskConfigurations implements Disposable {
});
jsonTasks[ind] = {
...jsonTasks[ind],
problemMatcher: problemMatchers.map(name => name.startsWith('$') ? name : `$${name}`)
...update
};
}
this.taskConfigurationManager.setTaskConfigurations(sourceFolderUri, jsonTasks);
} else { // task is not in `tasks.json`
task.problemMatcher = problemMatchers;
Object.keys(update).forEach(taskProperty => {
task[taskProperty] = update[taskProperty];
});
this.saveTask(sourceFolderUri, task);
}
}
Expand Down
50 changes: 47 additions & 3 deletions packages/task/src/browser/task-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-con
import { TaskSchemaUpdater } from './task-schema-updater';
import { TaskConfiguration, TaskWatcher } from '../common';
import { EditorManager } from '@theia/editor/lib/browser';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';

export namespace TaskCommands {
const TASK_CATEGORY = 'Task';
Expand All @@ -38,6 +39,18 @@ export namespace TaskCommands {
label: 'Run Task...'
};

export const TASK_RUN_BUILD: Command = {
id: 'task:run:build',
category: TASK_CATEGORY,
label: 'Run Build Task...'
};

export const TASK_RUN_TEST: Command = {
id: 'task:run:test',
category: TASK_CATEGORY,
label: 'Run Test Task...'
};

export const WORKBENCH_RUN_TASK: Command = {
id: 'workbench.action.tasks.runTask',
category: TASK_CATEGORY
Expand Down Expand Up @@ -135,6 +148,9 @@ export class TaskFrontendContribution implements CommandContribution, MenuContri
@inject(StatusBar)
protected readonly statusBar: StatusBar;

@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;

@postConstruct()
protected async init(): Promise<void> {
this.taskWatcher.onTaskCreated(() => this.updateRunningTasksItem());
Expand Down Expand Up @@ -209,6 +225,24 @@ export class TaskFrontendContribution implements CommandContribution, MenuContri
}
}
);
registry.registerCommand(
TaskCommands.TASK_RUN_BUILD,
{
isEnabled: () => this.workspaceService.opened,
// tslint:disable-next-line:no-any
execute: (...args: any[]) =>
this.quickOpenTask.runBuildOrTestTask('build')
}
);
registry.registerCommand(
TaskCommands.TASK_RUN_TEST,
{
isEnabled: () => this.workspaceService.opened,
// tslint:disable-next-line:no-any
execute: (...args: any[]) =>
this.quickOpenTask.runBuildOrTestTask('test')
}
);
registry.registerCommand(
TaskCommands.TASK_ATTACH,
{
Expand Down Expand Up @@ -268,20 +302,30 @@ export class TaskFrontendContribution implements CommandContribution, MenuContri
});

menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
commandId: TaskCommands.TASK_RUN_LAST.id,
commandId: TaskCommands.TASK_RUN_BUILD.id,
order: '1'
});

menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
commandId: TaskCommands.TASK_ATTACH.id,
commandId: TaskCommands.TASK_RUN_TEST.id,
order: '2'
});

menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
commandId: TaskCommands.TASK_RUN_TEXT.id,
commandId: TaskCommands.TASK_RUN_LAST.id,
order: '3'
});

menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
commandId: TaskCommands.TASK_ATTACH.id,
order: '4'
});

menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, {
commandId: TaskCommands.TASK_RUN_TEXT.id,
order: '5'
});

menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS_INFO, {
commandId: TaskCommands.TASK_SHOW_RUNNING.id,
label: 'Show Running Tasks...',
Expand Down
40 changes: 40 additions & 0 deletions packages/task/src/browser/task-schema-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export class TaskSchemaUpdater {
});
customizedDetectedTask.properties!.problemMatcher = problemMatcher;
customizedDetectedTask.properties!.options = commandOptionsSchema;
customizedDetectedTask.properties!.group = group;
customizedDetectedTasks.push(customizedDetectedTask);
});

Expand Down Expand Up @@ -227,6 +228,44 @@ const problemMatcher = {
}
]
};
const group = {
oneOf: [
{
type: 'string'
},
{
type: 'object',
properties: {
kind: {
type: 'string',
default: 'none',
description: 'The task\'s execution group.'
},
isDefault: {
type: 'boolean',
default: false,
description: 'Defines if this task is the default task in the group.'
}
}
}
],
enum: [
{ kind: 'build', isDefault: true },
{ kind: 'test', isDefault: true },
'build',
'test',
'none'
],
enumDescriptions: [
'Marks the task as the default build task.',
'Marks the task as the default test task.',
'Marks the task as a build task accessible through the \'Run Build Task\' command.',
'Marks the task as a test task accessible through the \'Run Test Task\' command.',
'Assigns the task to no group'
],
// tslint:disable-next-line:max-line-length
description: 'Defines to which execution group this task belongs to. It supports "build" to add it to the build group and "test" to add it to the test group.'
};

const processTaskConfigurationSchema: IJSONSchema = {
type: 'object',
Expand All @@ -250,6 +289,7 @@ const processTaskConfigurationSchema: IJSONSchema = {
description: 'Linux specific command configuration that overrides the default command, args, and options',
properties: commandAndArgs
},
group,
problemMatcher
}
};
Expand Down
Loading

0 comments on commit 94e2122

Please sign in to comment.