提交 4d645781 编写于 作者: D Dirk Baeumer

Fixes #29332: Improve workflow to assign a problem matcher to a build task.

上级 333d6581
......@@ -484,6 +484,7 @@
"problemMatchers": [
{
"name": "tsc",
"label": "%typescript.problemMatchers.tsc.label%",
"owner": "typescript",
"applyTo": "closedDocuments",
"fileLocation": [
......@@ -494,6 +495,7 @@
},
{
"name": "tsc-watch",
"label": "%typescript.problemMatchers.tscWatch.label%",
"owner": "typescript",
"applyTo": "closedDocuments",
"fileLocation": [
......
......@@ -39,5 +39,7 @@
"typescript.npm": "Specifies the path to the NPM executable used for Automatic Type Acquisition. Requires TypeScript >= 2.3.4.",
"typescript.check.npmIsInstalled": "Check if NPM is installed for Automatic Type Acquisition.",
"javascript.nameSuggestions": "Enable/disable including unique names from the file in JavaScript suggestion lists.",
"typescript.tsc.autoDetect": "Controls whether auto detection of tsc tasks is on or off."
"typescript.tsc.autoDetect": "Controls whether auto detection of tsc tasks is on or off.",
"typescript.problemMatchers.tsc.label": "TypeScript problems",
"typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)"
}
......@@ -115,10 +115,12 @@ export interface ProblemMatcher {
export interface NamedProblemMatcher extends ProblemMatcher {
name: string;
label: string;
}
export interface NamedMultiLineProblemPattern {
name: string;
label: string;
patterns: MultiLineProblemPattern;
}
......@@ -495,6 +497,11 @@ export namespace Config {
* The name of the problem pattern.
*/
name: string;
/**
* A human readable label
*/
label?: string;
}
export namespace NamedProblemPattern {
......@@ -518,6 +525,11 @@ export namespace Config {
*/
name: string;
/**
* A human readable label
*/
label?: string;
/**
* The actual patterns
*/
......@@ -663,6 +675,11 @@ export namespace Config {
* problem matchter from within a task.
*/
name?: string;
/**
* A human reable label.
*/
label?: string;
}
export function isNamedProblemMatcher(value: ProblemMatcher): value is NamedProblemMatcher {
......@@ -704,6 +721,7 @@ class ProblemPatternParser extends Parser {
private createNamedMultiLineProblemPattern(value: Config.NamedMultiLineProblemPattern): NamedMultiLineProblemPattern {
let result = {
name: value.name,
label: value.label ? value.label : value.name,
patterns: this.createMultiLineProblemPattern(value.patterns)
};
return result.patterns ? result : null;
......@@ -1229,7 +1247,8 @@ export class ProblemMatcherParser extends Parser {
}
}
if (Config.isNamedProblemMatcher(description)) {
(<NamedProblemMatcher>result).name = description.name;
(result as NamedProblemMatcher).name = description.name;
(result as NamedProblemMatcher).label = Types.isString(description.label) ? description.label : description.name;
}
return result;
}
......@@ -1466,9 +1485,13 @@ export namespace Schemas {
export const NamedProblemMatcher: IJSONSchema = Objects.clone(ProblemMatcher);
NamedProblemMatcher.properties = Objects.clone(NamedProblemMatcher.properties);
NamedProblemMatcher.properties['name'] = {
NamedProblemMatcher.properties.name = {
type: 'string',
description: localize('NamedProblemMatcherSchema.name', 'The name of the problem matcher.')
description: localize('NamedProblemMatcherSchema.name', 'The name of the problem matcher used to refer to it.')
};
NamedProblemMatcher.properties.label = {
type: 'string',
description: localize('NamedProblemMatcherSchema.label', 'A human readable label of the problem matcher.')
};
}
......@@ -1481,14 +1504,14 @@ let problemMatchersExtPoint = ExtensionsRegistry.registerExtensionPoint<Config.N
export interface IProblemMatcherRegistry {
onReady(): TPromise<void>;
exists(name: string): boolean;
get(name: string): ProblemMatcher;
values(): ProblemMatcher[];
get(name: string): NamedProblemMatcher;
values(): NamedProblemMatcher[];
keys(): string[];
}
class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
private matchers: IStringDictionary<ProblemMatcher>;
private matchers: IStringDictionary<NamedProblemMatcher>;
private readyPromise: TPromise<void>;
constructor() {
......@@ -1503,7 +1526,7 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
for (let matcher of problemMatchers) {
let result = parser.parse(matcher);
if (result && isNamedProblemMatcher(result)) {
this.add(result.name, result);
this.add(result);
}
}
});
......@@ -1522,11 +1545,11 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
return this.readyPromise;
}
public add(name: string, matcher: ProblemMatcher): void {
this.matchers[name] = matcher;
public add(matcher: NamedProblemMatcher): void {
this.matchers[matcher.name] = matcher;
}
public get(name: string): ProblemMatcher {
public get(name: string): NamedProblemMatcher {
return this.matchers[name];
}
......@@ -1542,19 +1565,23 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
return Object.keys(this.matchers);
}
public values(): ProblemMatcher[] {
public values(): NamedProblemMatcher[] {
return Object.keys(this.matchers).map(key => this.matchers[key]);
}
private fillDefaults(): void {
this.add('msCompile', {
this.add({
name: 'msCompile',
label: localize('msCompile', 'Microsoft compiler problems'),
owner: 'msCompile',
applyTo: ApplyToKind.allDocuments,
fileLocation: FileLocationKind.Absolute,
pattern: ProblemPatternRegistry.get('msCompile')
});
this.add('lessCompile', {
this.add({
name: 'lessCompile',
label: localize('lessCompile', 'Less problems'),
owner: 'lessCompile',
applyTo: ApplyToKind.allDocuments,
fileLocation: FileLocationKind.Absolute,
......@@ -1562,7 +1589,9 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
severity: Severity.Error
});
this.add('gulp-tsc', {
this.add({
name: 'gulp-tsc',
label: localize('gulp-tsc', 'Gulp TSC Problems'),
owner: 'typescript',
applyTo: ApplyToKind.closedDocuments,
fileLocation: FileLocationKind.Relative,
......@@ -1570,21 +1599,27 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
pattern: ProblemPatternRegistry.get('gulp-tsc')
});
this.add('jshint', {
this.add({
name: 'jshint',
label: localize('jshint', 'JSHint problems'),
owner: 'jshint',
applyTo: ApplyToKind.allDocuments,
fileLocation: FileLocationKind.Absolute,
pattern: ProblemPatternRegistry.get('jshint')
});
this.add('jshint-stylish', {
this.add({
name: 'jshint-stylish',
label: localize('jshint-stylish', 'JSHint stylish problems'),
owner: 'jshint',
applyTo: ApplyToKind.allDocuments,
fileLocation: FileLocationKind.Absolute,
pattern: ProblemPatternRegistry.get('jshint-stylish')
});
this.add('eslint-compact', {
this.add({
name: 'eslint-compact',
label: localize('eslint-compact', 'ESLint compact problems'),
owner: 'eslint',
applyTo: ApplyToKind.allDocuments,
fileLocation: FileLocationKind.Relative,
......@@ -1592,14 +1627,18 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry {
pattern: ProblemPatternRegistry.get('eslint-compact')
});
this.add('eslint-stylish', {
this.add({
name: 'eslint-stylish',
label: localize('eslint-stylish', 'ESLint stylish problems'),
owner: 'eslint',
applyTo: ApplyToKind.allDocuments,
fileLocation: FileLocationKind.Absolute,
pattern: ProblemPatternRegistry.get('eslint-stylish')
});
this.add('go', {
this.add({
name: 'go',
label: localize('go', 'Go problems'),
owner: 'go',
applyTo: ApplyToKind.allDocuments,
fileLocation: FileLocationKind.Relative,
......
......@@ -6,9 +6,11 @@
import nls = require('vs/nls');
import { TPromise } from 'vs/base/common/winjs.base';
import QuickOpen = require('vs/base/parts/quickopen/common/quickOpen');
import Model = require('vs/base/parts/quickopen/browser/quickOpenModel');
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { ProblemMatcherRegistry } from 'vs/platform/markers/common/problemMatcher';
import { Task, TaskGroup } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
......@@ -25,12 +27,14 @@ class TaskEntry extends base.TaskEntry {
return false;
}
let task = this._task;
this.taskService.run(task);
if (task.command.presentation.focus) {
this.quickOpenService.close();
return false;
if (task.problemMatchers === void 0 || task.problemMatchers.length === 0) {
this.attachProblemMatcher(task).then((task) => {
this.doRun(task);
});
return true;
} else {
return this.doRun(task);
}
return true;
}
}
......@@ -47,7 +51,7 @@ export class QuickOpenHandler extends base.QuickOpenHandler {
}
protected getTasks(): TPromise<Task[]> {
return this.taskService.getTasksForGroup(TaskGroup.Build);
return ProblemMatcherRegistry.onReady().then(() => this.taskService.getTasksForGroup(TaskGroup.Build));
}
protected createEntry(task: Task, highlights: Model.IHighlight[]): base.TaskEntry {
......
......@@ -9,16 +9,22 @@ import Filters = require('vs/base/common/filters');
import { TPromise } from 'vs/base/common/winjs.base';
import { Action, IAction } from 'vs/base/common/actions';
import { IStringDictionary } from 'vs/base/common/collections';
import * as Objects from 'vs/base/common/objects';
import Quickopen = require('vs/workbench/browser/quickopen');
import QuickOpen = require('vs/base/parts/quickopen/common/quickOpen');
import Model = require('vs/base/parts/quickopen/browser/quickOpenModel');
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { ProblemMatcherRegistry, NamedProblemMatcher } from 'vs/platform/markers/common/problemMatcher';
import { Task, TaskSourceKind } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
import { ActionBarContributor, ContributableActionProvider } from 'vs/workbench/browser/actions';
interface ProblemMatcherPickEntry extends IPickOpenEntry {
matcher: NamedProblemMatcher;
}
export class TaskEntry extends Model.QuickOpenEntry {
constructor(protected taskService: ITaskService, protected quickOpenService: IQuickOpenService, protected _task: Task, highlights: Model.IHighlight[] = []) {
......@@ -36,6 +42,44 @@ export class TaskEntry extends Model.QuickOpenEntry {
public get task(): Task {
return this._task;
}
protected attachProblemMatcher(task: Task): TPromise<Task> {
let entries: ProblemMatcherPickEntry[] = [];
for (let key of ProblemMatcherRegistry.keys()) {
let matcher = ProblemMatcherRegistry.get(key);
if (matcher.name === matcher.label) {
entries.push({ label: matcher.name, matcher: matcher });
} else {
entries.push({ label: nls.localize('entries', '{0} [${1}]', matcher.label, matcher.name), matcher: matcher });
}
}
if (entries.length > 0) {
entries.push({ label: 'Continue without scanning the build output', separator: { border: true }, matcher: undefined });
return this.quickOpenService.pick(entries, {
placeHolder: nls.localize('selectProblemMatcher', 'Select for which kind of errors and warnings to scan the build output')
}).then((selected) => {
if (selected && selected.matcher) {
let newTask = Objects.deepClone(task);
let matcherReference = `$${selected.matcher.name}`;
newTask.problemMatchers = [matcherReference];
this.taskService.customize(task, { problemMatcher: [matcherReference] }, true);
return newTask;
} else {
return task;
}
});
}
return TPromise.as(task);
}
protected doRun(task: Task): boolean {
this.taskService.run(task);
if (task.command.presentation.focus) {
this.quickOpenService.close();
return false;
}
return true;
}
}
export class TaskGroupEntry extends Model.QuickOpenEntryGroup {
......@@ -147,7 +191,7 @@ class CustomizeTaskAction extends Action {
}
public run(context: any): TPromise<any> {
return this.taskService.customize(this.task, true).then(() => {
return this.taskService.customize(this.task, undefined, true).then(() => {
this.quickOpenService.close();
});
}
......
......@@ -10,7 +10,7 @@ import QuickOpen = require('vs/base/parts/quickopen/common/quickOpen');
import Model = require('vs/base/parts/quickopen/browser/quickOpenModel');
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { Task } from 'vs/workbench/parts/tasks/common/tasks';
import { Task, TaskGroup } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
......@@ -27,12 +27,12 @@ class TaskEntry extends base.TaskEntry {
return false;
}
let task = this._task;
this.taskService.run(task);
if (task.command.presentation.focus) {
this.quickOpenService.close();
return false;
if (task.group === TaskGroup.Build && ((task.problemMatchers === void 0) || task.problemMatchers.length === 0)) {
this.attachProblemMatcher(task).then(task => this.doRun(task));
return true;
} else {
return this.doRun(task);
}
return true;
}
}
......
......@@ -45,7 +45,7 @@ export interface ITaskService extends IEventEmitter {
getTasksForGroup(group: string): TPromise<Task[]>;
getRecentlyUsedTasks(): LinkedMap<string, string>;
customize(task: Task, openConfig?: boolean): TPromise<void>;
customize(task: Task, properties?: { problemMatcher: string | string[] }, openConfig?: boolean): TPromise<void>;
registerTaskProvider(handle: number, taskProvider: ITaskProvider): void;
unregisterTaskProvider(handle: number): boolean;
......
......@@ -362,6 +362,10 @@ export enum ExecutionEngine {
Terminal = 2
}
export namespace ExecutionEngine {
export const _default: ExecutionEngine = ExecutionEngine.Terminal;
}
export enum JsonSchemaVersion {
V0_1_0 = 1,
V2_0_0 = 2
......
......@@ -610,7 +610,7 @@ class TaskService extends EventEmitter implements ITaskService {
? ExecutionEngine.Terminal
: this._taskSystem instanceof ProcessTaskSystem
? ExecutionEngine.Process
: undefined;
: ExecutionEngine._default;
if (currentExecutionEngine !== this.getExecutionEngine()) {
this.messageService.show(Severity.Info, nls.localize('TaskSystem.noHotSwap', 'Changing the task execution engine requires restarting VS Code. The change is ignored.'));
}
......@@ -820,7 +820,7 @@ class TaskService extends EventEmitter implements ITaskService {
return { configured, detected };
}
public customize(task: Task, openConfig: boolean = false): TPromise<void> {
public customize(task: Task, properties?: { problemMatcher: string | string[] }, openConfig?: boolean): TPromise<void> {
if (!ContributedTask.is(task)) {
return TPromise.as<void>(undefined);
}
......@@ -836,9 +836,19 @@ class TaskService extends EventEmitter implements ITaskService {
delete identifier['_key'];
Object.keys(identifier).forEach(key => customizes[key] = identifier[key]);
if (task.problemMatchers === void 0 || task.problemMatchers.length === 0) {
customizes.problemMatcher = [];
if (properties) {
for (let property of Object.getOwnPropertyNames(properties)) {
let value = properties[property];
if (value !== void 0 && value !== null) {
customizes[property] = value;
}
}
} else {
if (task.problemMatchers === void 0 || task.problemMatchers.length === 0) {
customizes.problemMatcher = [];
}
}
if (!fileConfig) {
fileConfig = {
version: '2.0.0',
......@@ -901,6 +911,8 @@ class TaskService extends EventEmitter implements ITaskService {
return undefined;
}
// We can only have extension tasks if we are in version 2.0.0. Then we can even run
// multiple build tasks.
if (extensionTasks.length === 1) {
return { task: extensionTasks[0], resolver };
} else {
......@@ -1152,7 +1164,7 @@ class TaskService extends EventEmitter implements ITaskService {
if (hasParseErrors) {
return TPromise.as({ set: undefined, hasErrors: true, configurations: undefined });
}
let engine = TaskConfig.ExecutionEngine._default;
let engine = ExecutionEngine._default;
if (config) {
engine = TaskConfig.ExecutionEngine.from(config);
if (engine === ExecutionEngine.Process) {
......@@ -1229,7 +1241,7 @@ class TaskService extends EventEmitter implements ITaskService {
private getExecutionEngine(): ExecutionEngine {
let { config } = this.getConfiguration();
if (!config) {
return ExecutionEngine.Terminal;
return ExecutionEngine._default;
}
return TaskConfig.ExecutionEngine.from(config);
}
......@@ -1429,9 +1441,8 @@ class TaskService extends EventEmitter implements ITaskService {
return;
}
this.getTasksForGroup(TaskGroup.Build).then((tasks) => {
let { configured, detected } = this.splitTasks(tasks);
let total = configured.length + detected.length;
if (total === 0) {
if (tasks.length === 0) {
// Show no build task message.
return;
}
this.quickOpenService.show('build task ');
......
......@@ -1473,8 +1473,6 @@ namespace Globals {
export namespace ExecutionEngine {
export const _default: Tasks.ExecutionEngine = Tasks.ExecutionEngine.Process;
export function from(config: ExternalTaskRunnerConfiguration): Tasks.ExecutionEngine {
let runner = config.runner || config._runner;
let result: Tasks.ExecutionEngine;
......@@ -1497,12 +1495,11 @@ export namespace ExecutionEngine {
throw new Error('Shouldn\'t happen.');
}
}
}
export namespace JsonSchemaVersion {
export const _default: Tasks.JsonSchemaVersion = Tasks.JsonSchemaVersion.V0_1_0;
const _default: Tasks.JsonSchemaVersion = Tasks.JsonSchemaVersion.V2_0_0;
export function from(config: ExternalTaskRunnerConfiguration): Tasks.JsonSchemaVersion {
let version = config.version;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册