diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index 1e576997a740c..2dd606d1fb2a6 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -13,6 +13,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ILineMatcher, createLineMatcher, ProblemMatcher, ProblemMatch, ApplyToKind, WatchingPattern, getResource } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IMarkerService, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { generateUuid } from 'vs/base/common/uuid'; +import { IFileService } from 'vs/platform/files/common/files'; export const enum ProblemCollectorEventKind { BackgroundProcessingBegins = 'backgroundProcessingBegins', @@ -30,7 +31,7 @@ namespace ProblemCollectorEvent { } export interface IProblemMatcher { - processLine(line: string): void; + processLine(line: string): Promise; } export class AbstractProblemCollector implements IDisposable { @@ -55,10 +56,10 @@ export class AbstractProblemCollector implements IDisposable { protected _onDidStateChange: Emitter; - constructor(problemMatchers: ProblemMatcher[], protected markerService: IMarkerService, private modelService: IModelService) { + constructor(problemMatchers: ProblemMatcher[], protected markerService: IMarkerService, private modelService: IModelService, fileService?: IFileService) { this.matchers = Object.create(null); this.bufferLength = 1; - problemMatchers.map(elem => createLineMatcher(elem)).forEach((matcher) => { + problemMatchers.map(elem => createLineMatcher(elem, fileService)).forEach((matcher) => { let length = matcher.matchLength; if (length > this.bufferLength) { this.bufferLength = length; @@ -143,14 +144,14 @@ export class AbstractProblemCollector implements IDisposable { return result; } - protected shouldApplyMatch(result: ProblemMatch): boolean { + protected async shouldApplyMatch(result: ProblemMatch): Promise { switch (result.description.applyTo) { case ApplyToKind.allDocuments: return true; case ApplyToKind.openDocuments: - return !!this.openModels[result.resource.toString()]; + return !!this.openModels[(await result.resource).toString()]; case ApplyToKind.closedDocuments: - return !this.openModels[result.resource.toString()]; + return !this.openModels[(await result.resource).toString()]; default: return true; } @@ -333,8 +334,8 @@ export class StartStopProblemCollector extends AbstractProblemCollector implemen private currentOwner: string; private currentResource: string; - constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, _strategy: ProblemHandlingStrategy = ProblemHandlingStrategy.Clean) { - super(problemMatchers, markerService, modelService); + constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, _strategy: ProblemHandlingStrategy = ProblemHandlingStrategy.Clean, fileService?: IFileService) { + super(problemMatchers, markerService, modelService, fileService); let ownerSet: { [key: string]: boolean; } = Object.create(null); problemMatchers.forEach(description => ownerSet[description.owner] = true); this.owners = Object.keys(ownerSet); @@ -343,17 +344,17 @@ export class StartStopProblemCollector extends AbstractProblemCollector implemen }); } - public processLine(line: string): void { + public async processLine(line: string): Promise { let markerMatch = this.tryFindMarker(line); if (!markerMatch) { return; } let owner = markerMatch.description.owner; - let resource = markerMatch.resource; + let resource = await markerMatch.resource; let resourceAsString = resource.toString(); this.removeResourceToClean(owner, resourceAsString); - let shouldApplyMatch = this.shouldApplyMatch(markerMatch); + let shouldApplyMatch = await this.shouldApplyMatch(markerMatch); if (shouldApplyMatch) { this.recordMarker(markerMatch.marker, owner, resourceAsString); if (this.currentOwner !== owner || this.currentResource !== resourceAsString) { @@ -386,8 +387,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement private currentOwner: string | null; private currentResource: string | null; - constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService) { - super(problemMatchers, markerService, modelService); + constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, fileService?: IFileService) { + super(problemMatchers, markerService, modelService, fileService); this.problemMatchers = problemMatchers; this.resetCurrentResource(); this.backgroundPatterns = []; @@ -415,7 +416,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement } } - public processLine(line: string): void { + public async processLine(line: string): Promise { if (this.tryBegin(line) || this.tryFinish(line)) { return; } @@ -423,11 +424,11 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement if (!markerMatch) { return; } - let resource = markerMatch.resource; + let resource = await markerMatch.resource; let owner = markerMatch.description.owner; let resourceAsString = resource.toString(); this.removeResourceToClean(owner, resourceAsString); - let shouldApplyMatch = this.shouldApplyMatch(markerMatch); + let shouldApplyMatch = await this.shouldApplyMatch(markerMatch); if (shouldApplyMatch) { this.recordMarker(markerMatch.marker, owner, resourceAsString); if (this.currentOwner !== owner || this.currentResource !== resourceAsString) { @@ -442,7 +443,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement this.reportMarkersForCurrentResource(); } - private tryBegin(line: string): boolean { + private async tryBegin(line: string): Promise { let result = false; for (const background of this.backgroundPatterns) { let matches = background.begin.regexp.exec(line); @@ -459,7 +460,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement let file = matches[background.begin.file!]; if (file) { let resource = getResource(file, background.matcher); - this.recordResourceToClean(owner, resource); + this.recordResourceToClean(owner, await resource); } else { this.recordResourcesToClean(owner); } diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index 2f0a4cc5accee..69ec86207803e 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -21,11 +21,13 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { Event, Emitter } from 'vs/base/common/event'; +import { IFileService, IFileStat } from 'vs/platform/files/common/files'; export enum FileLocationKind { - Auto, + Default, Relative, - Absolute + Absolute, + AutoDetect } export module FileLocationKind { @@ -35,6 +37,8 @@ export module FileLocationKind { return FileLocationKind.Absolute; } else if (value === 'relative') { return FileLocationKind.Relative; + } else if (value === 'autodetect') { + return FileLocationKind.AutoDetect; } else { return undefined; } @@ -172,7 +176,7 @@ interface ProblemData { } export interface ProblemMatch { - resource: URI; + resource: Promise; marker: IMarkerData; description: ProblemMatcher; } @@ -182,13 +186,32 @@ export interface HandleResult { continue: boolean; } -export function getResource(filename: string, matcher: ProblemMatcher): URI { + +export async function getResource(filename: string, matcher: ProblemMatcher, fileService?: IFileService): Promise { let kind = matcher.fileLocation; let fullPath: string | undefined; if (kind === FileLocationKind.Absolute) { fullPath = filename; } else if ((kind === FileLocationKind.Relative) && matcher.filePrefix) { fullPath = join(matcher.filePrefix, filename); + } else if (kind === FileLocationKind.AutoDetect) { + const matcherClone = Objects.deepClone(matcher); + matcherClone.fileLocation = FileLocationKind.Relative; + if (fileService) { + const relative = await getResource(filename, matcherClone); + let stat: IFileStat | undefined = undefined; + try { + stat = await fileService.resolve(relative); + } catch (ex) { + // Do nothing, we just need to catch file resolution errors. + } + if (stat) { + return relative; + } + } + + matcherClone.fileLocation = FileLocationKind.Absolute; + return getResource(filename, matcherClone); } if (fullPath === undefined) { throw new Error('FileLocationKind is not actionable. Does the matcher have a filePrefix? This should never happen.'); @@ -210,12 +233,12 @@ export interface ILineMatcher { handle(lines: string[], start?: number): HandleResult; } -export function createLineMatcher(matcher: ProblemMatcher): ILineMatcher { +export function createLineMatcher(matcher: ProblemMatcher, fileService?: IFileService): ILineMatcher { let pattern = matcher.pattern; if (Types.isArray(pattern)) { - return new MultiLineMatcher(matcher); + return new MultiLineMatcher(matcher, fileService); } else { - return new SingleLineMatcher(matcher); + return new SingleLineMatcher(matcher, fileService); } } @@ -223,9 +246,11 @@ const endOfLine: string = Platform.OS === Platform.OperatingSystem.Windows ? '\r abstract class AbstractLineMatcher implements ILineMatcher { private matcher: ProblemMatcher; + private fileService?: IFileService; - constructor(matcher: ProblemMatcher) { + constructor(matcher: ProblemMatcher, fileService?: IFileService) { this.matcher = matcher; + this.fileService = fileService; } public handle(lines: string[], start: number = 0): HandleResult { @@ -312,8 +337,8 @@ abstract class AbstractLineMatcher implements ILineMatcher { return undefined; } - protected getResource(filename: string): URI { - return getResource(filename, this.matcher); + protected getResource(filename: string): Promise { + return getResource(filename, this.matcher, this.fileService); } private getLocation(data: ProblemData): Location | null { @@ -389,8 +414,8 @@ class SingleLineMatcher extends AbstractLineMatcher { private pattern: ProblemPattern; - constructor(matcher: ProblemMatcher) { - super(matcher); + constructor(matcher: ProblemMatcher, fileService?: IFileService) { + super(matcher, fileService); this.pattern = matcher.pattern; } @@ -425,8 +450,8 @@ class MultiLineMatcher extends AbstractLineMatcher { private patterns: ProblemPattern[]; private data: ProblemData | null; - constructor(matcher: ProblemMatcher) { - super(matcher); + constructor(matcher: ProblemMatcher, fileService?: IFileService) { + super(matcher, fileService); this.patterns = matcher.pattern; } @@ -1345,7 +1370,7 @@ export class ProblemMatcherParser extends Parser { kind = FileLocationKind.fromString(description.fileLocation); if (kind) { fileLocation = kind; - if (kind === FileLocationKind.Relative) { + if ((kind === FileLocationKind.Relative) || (kind === FileLocationKind.AutoDetect)) { filePrefix = '${workspaceFolder}'; } } @@ -1355,7 +1380,7 @@ export class ProblemMatcherParser extends Parser { kind = FileLocationKind.fromString(values[0]); if (values.length === 1 && kind === FileLocationKind.Absolute) { fileLocation = kind; - } else if (values.length === 2 && kind === FileLocationKind.Relative && values[1]) { + } else if (values.length === 2 && (kind === FileLocationKind.Relative || kind === FileLocationKind.AutoDetect) && values[1]) { fileLocation = kind; filePrefix = values[1]; } @@ -1573,7 +1598,7 @@ export namespace Schemas { oneOf: [ { type: 'string', - enum: ['absolute', 'relative'] + enum: ['absolute', 'relative', 'autoDetect'] }, { type: 'array', diff --git a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts index 9f1d9500cddb7..9151937e337fa 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts @@ -1297,7 +1297,7 @@ class TaskService extends Disposable implements ITaskService { this.terminalService, this.outputService, this.panelService, this.markerService, this.modelService, this.configurationResolverService, this.telemetryService, this.contextService, this.environmentService, - TaskService.OutputChannelId, + TaskService.OutputChannelId, this.fileService, (workspaceFolder: IWorkspaceFolder) => { if (!workspaceFolder) { return undefined; diff --git a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts index 4c647da905752..b73865f898cfc 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts @@ -17,7 +17,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { isUNC } from 'vs/base/common/extpath'; import { win32 } from 'vs/base/node/processes'; - +import { IFileService } from 'vs/platform/files/common/files'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -29,7 +29,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; -import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind } from 'vs/workbench/contrib/tasks/common/problemCollectors'; +import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { Task, CustomTask, ContributedTask, RevealKind, CommandOptions, ShellConfiguration, RuntimeType, PanelKind, TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope, RevealProblemKind @@ -171,6 +171,7 @@ export class TerminalTaskSystem implements ITaskSystem { private contextService: IWorkspaceContextService, private environmentService: IWorkbenchEnvironmentService, private outputChannelId: string, + private fileService: IFileService, taskSystemInfoResolver: TaskSystemInfoResovler, ) { @@ -509,7 +510,7 @@ export class TerminalTaskSystem implements ITaskSystem { if (task.configurationProperties.isBackground) { promise = new Promise((resolve, reject) => { const problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); - let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService); + let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService, this.fileService); let toDispose: IDisposable[] | undefined = []; let eventCounter: number = 0; toDispose.push(watchingProblemMatcher.onDidStateChange((event) => { @@ -554,13 +555,14 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); const onData = terminal.onLineData((line) => { - watchingProblemMatcher.processLine(line); - if (!delayer) { - delayer = new Async.Delayer(3000); - } - delayer.trigger(() => { - watchingProblemMatcher.forceDelivery(); - delayer = undefined; + return watchingProblemMatcher.processLine(line).then(() => { + if (!delayer) { + delayer = new Async.Delayer(3000); + } + delayer.trigger(() => { + watchingProblemMatcher.forceDelivery(); + delayer = undefined; + }); }); }); const onExit = terminal.onExit((exitCode) => { @@ -630,10 +632,10 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); let problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); - let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService); + let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService, ProblemHandlingStrategy.Clean, this.fileService); const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); const onData = terminal.onLineData((line) => { - startStopProblemMatcher.processLine(line); + return startStopProblemMatcher.processLine(line); }); const onExit = terminal.onExit((exitCode) => { onData.dispose(); diff --git a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts index 6d27ba51b44b7..17744b20a9ee3 100644 --- a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts @@ -258,15 +258,16 @@ export class ProcessTaskSystem implements ITaskSystem { const onProgress = (progress: LineData) => { let line = Strings.removeAnsiEscapeCodes(progress.line); this.appendOutput(line + '\n'); - watchingProblemMatcher.processLine(line); - if (delayer === null) { - delayer = new Async.Delayer(3000); - } - delayer.trigger(() => { - watchingProblemMatcher.forceDelivery(); - return null; - }).then(() => { - delayer = null; + return watchingProblemMatcher.processLine(line).then(() => { + if (delayer === null) { + delayer = new Async.Delayer(3000); + } + delayer.trigger(() => { + watchingProblemMatcher.forceDelivery(); + return null; + }).then(() => { + delayer = null; + }); }); }; const startPromise = this.childProcess.start(onProgress); @@ -322,7 +323,7 @@ export class ProcessTaskSystem implements ITaskSystem { const onProgress = (progress: LineData) => { let line = Strings.removeAnsiEscapeCodes(progress.line); this.appendOutput(line + '\n'); - startStopProblemMatcher.processLine(line); + return startStopProblemMatcher.processLine(line); }; const startPromise = this.childProcess.start(onProgress); this.childProcess.pid.then(pid => {