Skip to content

Commit

Permalink
create problem markers from task output
Browse files Browse the repository at this point in the history
What's included in this PR:
- support taskDefinition, problemMatcher, & problemPattern contributed by Theia plugins.
- support customizing tasks by adding named & anonymous problem matchers and patterns in .theia/tasks.json.
- support parsing output from both "start - finish" tasks and "watch" tasks.
- support both single line and multi line patterns.
- create problem markers on parsing task output with the plugin-defined or user-defined problem matchers and patterns.

What's not included:
- prompt users to provide problem matcher and / or patterns before starting tasks
- remove problem markers automatically when problems are fixed

Signed-off-by: elaihau <liang.huang@ericsson.com>
  • Loading branch information
elaihau committed May 14, 2019
1 parent 2b95010 commit b53bc0e
Show file tree
Hide file tree
Showing 28 changed files with 2,087 additions and 71 deletions.
23 changes: 21 additions & 2 deletions packages/cpp/src/browser/cpp-task-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
********************************************************************************/

import parseArgv = require('string-argv');
import { inject, injectable } from 'inversify';
import { inject, injectable, postConstruct } from 'inversify';
import { ProcessTaskConfiguration } from '@theia/task/lib/common/process/task-protocol';
import { TaskContribution, TaskProvider, TaskProviderRegistry, TaskResolver, TaskResolverRegistry } from '@theia/task/lib/browser/task-contribution';
import { CppBuildConfigurationManager, CppBuildConfiguration } from './cpp-build-configurations';
import { ContributedTaskConfiguration, TaskConfiguration } from '@theia/task/lib/common/task-protocol';
import { ContributedTaskConfiguration, TaskConfiguration, TaskDefinitionRegistry } from '@theia/task/lib/common/task-protocol';

/**
* Data required to define a C/C++ build task the user could run.
Expand All @@ -35,8 +35,14 @@ const CPP_BUILD_TASK_SOURCE: string = 'cpp';
export class CppTaskProvider implements TaskContribution, TaskProvider, TaskResolver {

@inject(TaskResolverRegistry) protected readonly taskResolverRegistry: TaskResolverRegistry;
@inject(TaskDefinitionRegistry) protected readonly taskDefinitionRegistry: TaskDefinitionRegistry;
@inject(CppBuildConfigurationManager) protected readonly cppBuildConfigurationManager: CppBuildConfigurationManager;

@postConstruct()
protected init(): void {
this.registerTaskDefinition();
}

registerProviders(registry: TaskProviderRegistry) {
registry.register(CPP_BUILD_TASK_SOURCE, this);
}
Expand Down Expand Up @@ -108,4 +114,17 @@ export class CppTaskProvider implements TaskContribution, TaskProvider, TaskReso

return taskConfigs;
}

private registerTaskDefinition(): void {
this.taskDefinitionRegistry.register({
type: CPP_BUILD_TASK_TYPE_KEY,
required: ['label'],
properties: {
label: {
type: 'string',
description: 'Lable of the cpp build configuration task'
}
}
}, CPP_BUILD_TASK_SOURCE);
}
}
7 changes: 7 additions & 0 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ExtPluginApi } from './plugin-ext-api-contribution';
import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema';
import { RecursivePartial } from '@theia/core/lib/common/types';
import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/common/preferences/preference-schema';
import { TaskDefinitionContribution, ProblemMatcherContribution, ProblemPatternContribution } from '@theia/task/lib/common';

export const hostedServicePath = '/services/hostedPlugin';

Expand Down Expand Up @@ -71,6 +72,9 @@ export interface PluginPackageContribution {
keybindings?: PluginPackageKeybinding[];
debuggers?: PluginPackageDebuggersContribution[];
snippets: PluginPackageSnippetsContribution[];
taskDefinitions?: TaskDefinitionContribution[];
problemMatchers?: ProblemMatcherContribution[];
problemPatterns?: ProblemPatternContribution[];
}

export interface PluginPackageViewContainer {
Expand Down Expand Up @@ -364,6 +368,9 @@ export interface PluginContribution {
keybindings?: Keybinding[];
debuggers?: DebuggerContribution[];
snippets?: SnippetContribution[];
taskDefinitions?: TaskDefinitionContribution[];
problemMatchers?: ProblemMatcherContribution[];
problemPatterns?: ProblemPatternContribution[];
}

export interface SnippetContribution {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/hosted/browser/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export class HostedPluginSupport {
}

if (plugin.model.contributes) {
this.contributionHandler.handleContributions(plugin.model.contributes);
this.contributionHandler.handleContributions(plugin.model.contributes, plugin.model.id);
}
}

Expand Down
13 changes: 13 additions & 0 deletions packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { deepClone } from '@theia/core/lib/common/objects';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/common/preferences/preference-schema';
import { RecursivePartial } from '@theia/core/lib/common/types';
import { TaskDefinitionContribution, ProblemMatcherContribution, ProblemPatternContribution } from '@theia/task/lib/common/task-protocol';

namespace nls {
export function localize(key: string, _default: string) {
Expand Down Expand Up @@ -183,6 +184,18 @@ export class TheiaPluginScanner implements PluginScanner {
contributions.debuggers = debuggers;
}

if (rawPlugin.contributes!.taskDefinitions) {
contributions.taskDefinitions = rawPlugin.contributes!.taskDefinitions as TaskDefinitionContribution[];
}

if (rawPlugin.contributes!.problemMatchers) {
contributions.problemMatchers = rawPlugin.contributes!.problemMatchers as ProblemMatcherContribution[];
}

if (rawPlugin.contributes!.problemPatterns) {
contributions.problemPatterns = rawPlugin.contributes!.problemPatterns as ProblemPatternContribution[];
}

contributions.snippets = this.readSnippets(rawPlugin);
return contributions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { KeybindingsContributionPointHandler } from './keybindings/keybindings-c
import { MonacoSnippetSuggestProvider } from '@theia/monaco/lib/browser/monaco-snippet-suggest-provider';
import { PluginSharedStyle } from './plugin-shared-style';
import { CommandRegistry } from '@theia/core';
import { TaskDefinitionRegistry, ProblemMatcherRegistry, ProblemPatternRegistry } from '@theia/task/lib/common';

@injectable()
export class PluginContributionHandler {
Expand Down Expand Up @@ -59,7 +60,16 @@ export class PluginContributionHandler {
@inject(PluginSharedStyle)
protected readonly style: PluginSharedStyle;

handleContributions(contributions: PluginContribution): void {
@inject(TaskDefinitionRegistry)
protected readonly taskDefinitionRegistry: TaskDefinitionRegistry;

@inject(ProblemMatcherRegistry)
protected readonly problemMatcherRegistry: ProblemMatcherRegistry;

@inject(ProblemPatternRegistry)
protected readonly problemPatternRegistry: ProblemPatternRegistry;

handleContributions(contributions: PluginContribution, modelId: string): void {
if (contributions.configuration) {
this.updateConfigurationSchema(contributions.configuration);
}
Expand Down Expand Up @@ -149,6 +159,18 @@ export class PluginContributionHandler {
});
}
}

if (contributions.taskDefinitions) {
contributions.taskDefinitions.forEach(def => this.taskDefinitionRegistry.register(def, modelId));
}

if (contributions.problemPatterns) {
contributions.problemPatterns.forEach(pattern => this.problemPatternRegistry.register(pattern.name!, pattern));
}

if (contributions.problemMatchers) {
contributions.problemMatchers.forEach(matcher => this.problemMatcherRegistry.register(matcher));
}
}

protected registerCommands(contribution: PluginContribution): void {
Expand Down
6 changes: 4 additions & 2 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1728,12 +1728,14 @@ export class Task {
if (this.taskExecution instanceof ProcessExecution) {
Object.assign(this.taskDefinition, {
type: 'process',
id: this.taskExecution.computeId()
id: this.taskExecution.computeId(),
taskType: this.taskDefinition!.type
});
} else if (this.taskExecution instanceof ShellExecution) {
Object.assign(this.taskDefinition, {
type: 'shell',
id: this.taskExecution.computeId()
id: this.taskExecution.computeId(),
taskType: this.taskDefinition!.type
});
}
}
Expand Down
1 change: 0 additions & 1 deletion packages/process/src/node/terminal-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,4 @@ export class TerminalProcess extends Process {
throw new Error('pty process did not start correctly');
}
}

}
3 changes: 2 additions & 1 deletion packages/task/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"@theia/terminal": "^0.6.0",
"@theia/variable-resolver": "^0.6.0",
"@theia/workspace": "^0.6.0",
"jsonc-parser": "^2.0.2"
"jsonc-parser": "^2.0.2",
"vscode-uri": "^1.0.1"
},
"publishConfig": {
"access": "public"
Expand Down
45 changes: 27 additions & 18 deletions packages/task/src/browser/quick-open-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
QuickOpenGroupItem, QuickOpenMode, QuickOpenHandler, QuickOpenOptions, QuickOpenActionProvider, QuickOpenGroupItemOptions
} from '@theia/core/lib/browser/quick-open/';
import { TaskService } from './task-service';
import { ContributedTaskConfiguration, TaskInfo, TaskConfiguration } from '../common/task-protocol';
import { TaskInfo, TaskConfiguration, TaskDefinitionRegistry } from '../common/task-protocol';
import { TaskConfigurations } from './task-configurations';
import URI from '@theia/core/lib/common/uri';
import { TaskActionProvider } from './task-action-provider';
Expand Down Expand Up @@ -58,6 +58,9 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler {
@inject(TaskConfigurations)
protected readonly taskConfigurations: TaskConfigurations;

@inject(TaskDefinitionRegistry)
protected readonly taskDefinitionRegistry: TaskDefinitionRegistry;

/** Initialize this quick open model with the tasks. */
async init(): Promise<void> {
const recentTasks = this.taskService.recentTasks;
Expand All @@ -69,29 +72,38 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler {
const isMulti = stat ? !stat.isDirectory : false;
this.items = [];
this.items.push(
...filteredRecentTasks.map((task, index) =>
new TaskRunQuickOpenItem(task, this.taskService, isMulti, {
...filteredRecentTasks.map((task, index) => {
const item = new TaskRunQuickOpenItem(task, this.taskService, isMulti, {
groupLabel: index === 0 ? 'recently used tasks' : undefined,
showBorder: false
})),
...filteredConfiguredTasks.map((task, index) =>
new TaskRunQuickOpenItem(task, this.taskService, isMulti, {
});
item['taskDefinitionRegistry'] = this.taskDefinitionRegistry;
return item;
}),
...filteredConfiguredTasks.map((task, index) => {
const item = new TaskRunQuickOpenItem(task, this.taskService, isMulti, {
groupLabel: index === 0 ? 'configured tasks' : undefined,
showBorder: (
filteredRecentTasks.length <= 0
? false
: index === 0 ? true : false
)
})),
...filteredProvidedTasks.map((task, index) =>
new TaskRunQuickOpenItem(task, this.taskService, isMulti, {
});
item['taskDefinitionRegistry'] = this.taskDefinitionRegistry;
return item;
}),
...filteredProvidedTasks.map((task, index) => {
const item = new TaskRunQuickOpenItem(task, this.taskService, isMulti, {
groupLabel: index === 0 ? 'detected tasks' : undefined,
showBorder: (
filteredRecentTasks.length <= 0 && filteredConfiguredTasks.length <= 0
? false
: index === 0 ? true : false
)
}))
});
item['taskDefinitionRegistry'] = this.taskDefinitionRegistry;
return item;
})
);

this.actionProvider = this.items.length ? this.taskActionProvider : undefined;
Expand Down Expand Up @@ -221,6 +233,8 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler {

export class TaskRunQuickOpenItem extends QuickOpenGroupItem {

protected taskDefinitionRegistry: TaskDefinitionRegistry;

constructor(
protected readonly task: TaskConfiguration,
protected taskService: TaskService,
Expand All @@ -235,7 +249,7 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem {
}

getLabel(): string {
if (ContributedTaskConfiguration.is(this.task)) {
if (this.taskDefinitionRegistry && !!this.taskDefinitionRegistry.getDefinition(this.task)) {
return `${this.task._source}: ${this.task.label}`;
}
return `${this.task.type}: ${this.task.label}`;
Expand All @@ -249,7 +263,7 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem {
if (!this.isMulti) {
return '';
}
if (ContributedTaskConfiguration.is(this.task)) {
if (this.taskDefinitionRegistry && !!this.taskDefinitionRegistry.getDefinition(this.task)) {
if (this.task._scope) {
return new URI(this.task._scope).path.toString();
}
Expand All @@ -265,12 +279,7 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem {
return false;
}

if (ContributedTaskConfiguration.is(this.task)) {
this.taskService.run(this.task._source, this.task.label);
} else {
this.taskService.runConfiguredTask(this.task._source, this.task.label);
}

this.taskService.run(this.task._source, this.task.label);
return true;
}
}
Expand Down
43 changes: 37 additions & 6 deletions packages/task/src/browser/task-configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
********************************************************************************/

import { inject, injectable } from 'inversify';
import { TaskConfiguration } from '../common/task-protocol';
import { TaskConfiguration, TaskCustomization, TaskDefinitionRegistry, ContributedTaskConfiguration } from '../common';
import { Disposable, DisposableCollection, ResourceProvider } from '@theia/core/lib/common';
import URI from '@theia/core/lib/common/uri';
import { FileSystemWatcher, FileChangeEvent } from '@theia/filesystem/lib/browser/filesystem-watcher';
Expand Down Expand Up @@ -47,6 +47,8 @@ export class TaskConfigurations implements Disposable {
* For the inner map (i.e., task config map), the key is task label and value TaskConfiguration
*/
protected tasksMap = new Map<string, Map<string, TaskConfiguration>>();
protected taskCustomizations: TaskCustomization[] = [];

protected watchedConfigFileUris: string[] = [];
protected watchersMap = new Map<string, Disposable>(); // map of watchers for task config files, where the key is folder uri

Expand All @@ -66,6 +68,9 @@ export class TaskConfigurations implements Disposable {
@inject(OpenerService)
protected readonly openerService: OpenerService;

@inject(TaskDefinitionRegistry)
protected readonly taskDefinitionRegistry: TaskDefinitionRegistry;

constructor(
@inject(FileSystemWatcher) protected readonly watcherServer: FileSystemWatcher,
@inject(FileSystem) protected readonly fileSystem: FileSystem
Expand Down Expand Up @@ -173,6 +178,10 @@ export class TaskConfigurations implements Disposable {
this.tasksMap.delete(source);
}

getTaskCustomizations(type: string): TaskCustomization[] {
return this.taskCustomizations.filter(c => c.type === type);
}

/** returns the string uri of where the config file would be, if it existed under a given root directory */
protected getConfigFileUri(rootDir: string): string {
return new URI(rootDir).resolve(this.TASKFILEPATH).resolve(this.TASKFILE).toString();
Expand All @@ -198,22 +207,38 @@ export class TaskConfigurations implements Disposable {
* If reading a config file wasn't successful then does nothing.
*/
protected async refreshTasks(configFileUri: string) {
const tasksConfigsArray = await this.readTasks(configFileUri);
if (tasksConfigsArray) {
const tasksArray = await this.readTasks(configFileUri);
if (tasksArray) {
const configuredTasksArray: TaskConfiguration[] = [];
const customizations: TaskCustomization[] = [];

tasksArray.forEach(t => {
if (this.isConfiguredTask(t)) {
customizations.push(t);
} else {
configuredTasksArray.push(t);
}
});

// only clear tasks map when successful at parsing the config file
// this way we avoid clearing and re-filling it multiple times if the
// user is editing the file in the auto-save mode, having momentarily
// non-parsing JSON.
this.removeTasks(configFileUri);

if (tasksConfigsArray.length > 0) {
if (configuredTasksArray.length > 0) {
const newTaskMap = new Map<string, TaskConfiguration>();
for (const task of tasksConfigsArray) {
for (const task of configuredTasksArray) {
newTaskMap.set(task.label, task);
}
const source = this.getSourceFolderFromConfigUri(configFileUri);
this.tasksMap.set(source, newTaskMap);
}

if (customizations.length > 0) {
this.taskCustomizations.length = 0;
this.taskCustomizations = customizations;
}
}
}

Expand Down Expand Up @@ -288,7 +313,7 @@ export class TaskConfigurations implements Disposable {
protected filterDuplicates(tasks: TaskConfiguration[]): TaskConfiguration[] {
const filteredTasks: TaskConfiguration[] = [];
for (const task of tasks) {
if (filteredTasks.some(t => t.label === task.label)) {
if (filteredTasks.some(t => !this.isConfiguredTask(t) && t.label === task.label)) {
// TODO: create a problem marker so that this issue will be visible in the editor?
console.error(`Error parsing ${this.TASKFILE}: found duplicate entry for label: ${task.label}`);
} else {
Expand All @@ -301,4 +326,10 @@ export class TaskConfigurations implements Disposable {
private getSourceFolderFromConfigUri(configFileUri: string): string {
return new URI(configFileUri).parent.parent.path.toString();
}

private isConfiguredTask(task: TaskConfiguration): task is ContributedTaskConfiguration {
const taskDefinition = this.taskDefinitionRegistry.getDefinition(task);
// it is considered as a customization if the task definition registry finds a def for the task configuration
return !!taskDefinition;
}
}
Loading

0 comments on commit b53bc0e

Please sign in to comment.