diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 9636fcda22d081fa7d1be28bc81ee838187071c1..d70825157bd62fc141a4b2c91af87fa7e3ad104d 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -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": [ diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index 9dbfd2d45647127e8e2cbcb7d6488071f584ca47..890ac8e8ef6d6f8f3d206590d8900ce8398cc250 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -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)" } diff --git a/src/vs/platform/markers/common/problemMatcher.ts b/src/vs/platform/markers/common/problemMatcher.ts index 055e46fecaa48102fbc93208aaef855e7ef6ac96..7e8507981dc98a25a2dbbcb3ff7ecfb2411c4d2e 100644 --- a/src/vs/platform/markers/common/problemMatcher.ts +++ b/src/vs/platform/markers/common/problemMatcher.ts @@ -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)) { - (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; 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; + private matchers: IStringDictionary; private readyPromise: TPromise; 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, diff --git a/src/vs/workbench/parts/tasks/browser/buildQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/buildQuickOpen.ts index 22e0bd0cd00048a37b0086e8c1e8c3ef6d5ce5d6..1e328b1d2b03d9d8c184f1ef315338f8debfde3b 100644 --- a/src/vs/workbench/parts/tasks/browser/buildQuickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/buildQuickOpen.ts @@ -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 { - return this.taskService.getTasksForGroup(TaskGroup.Build); + return ProblemMatcherRegistry.onReady().then(() => this.taskService.getTasksForGroup(TaskGroup.Build)); } protected createEntry(task: Task, highlights: Model.IHighlight[]): base.TaskEntry { diff --git a/src/vs/workbench/parts/tasks/browser/quickOpen.ts b/src/vs/workbench/parts/tasks/browser/quickOpen.ts index 7438f9900017cd2965670744dd983c1afcb3bdf6..cce6efe8d1a681862e3d9da7a316ba8eda84c698 100644 --- a/src/vs/workbench/parts/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/quickOpen.ts @@ -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 { + 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 { - return this.taskService.customize(this.task, true).then(() => { + return this.taskService.customize(this.task, undefined, true).then(() => { this.quickOpenService.close(); }); } diff --git a/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts index e48d1d7d55cb094ad0f3ebf47f6014222717e2b7..547db2b365ded4cb1c2e2280117da9d42b3d54fd 100644 --- a/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts @@ -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; } } diff --git a/src/vs/workbench/parts/tasks/common/taskService.ts b/src/vs/workbench/parts/tasks/common/taskService.ts index 4010260bef8cbb603a19e745e845b84c1f74bc70..82a5c8bfdca10eb1437930bf90c5c05d7dd3b7ca 100644 --- a/src/vs/workbench/parts/tasks/common/taskService.ts +++ b/src/vs/workbench/parts/tasks/common/taskService.ts @@ -45,7 +45,7 @@ export interface ITaskService extends IEventEmitter { getTasksForGroup(group: string): TPromise; getRecentlyUsedTasks(): LinkedMap; - customize(task: Task, openConfig?: boolean): TPromise; + customize(task: Task, properties?: { problemMatcher: string | string[] }, openConfig?: boolean): TPromise; registerTaskProvider(handle: number, taskProvider: ITaskProvider): void; unregisterTaskProvider(handle: number): boolean; diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index deaee2789eb6b7d2192b58c17871434f09d7b977..2d6c3dcf1838bd2ecd8ac36aa501d0182ef134aa 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -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 diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 50810ac1fc7a0768f181ef99a27c7d2062145a11..3f39cc9829f86f75d7ac1d1e0d364c1f3b124939 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -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 { + public customize(task: Task, properties?: { problemMatcher: string | string[] }, openConfig?: boolean): TPromise { if (!ContributedTask.is(task)) { return TPromise.as(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 '); diff --git a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts index 8b42ae1826a8804559fa45445fd1c21d568b3067..e2780233d4bfc7007c50e1e7092cd7188225bfb5 100644 --- a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts @@ -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;