Skip to content

Commit 7fc3b1f

Browse files
authored
🎁 Add search to file location methods in tasks (microsoft#165156)
1 parent 245c0c7 commit 7fc3b1f

File tree

2 files changed

+151
-11
lines changed

2 files changed

+151
-11
lines changed

src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import * as resources from 'vs/base/common/resources';
1616
import Severity from 'vs/base/common/severity';
1717
import * as Types from 'vs/base/common/types';
1818
import * as nls from 'vs/nls';
19+
import { asArray } from 'vs/base/common/arrays';
1920

2021
import { IModelService } from 'vs/editor/common/services/model';
2122
import { IFileService } from 'vs/platform/files/common/files';
@@ -1648,7 +1649,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
16481649
matcher = value;
16491650
}
16501651
if (matcher && matcher.filePrefix) {
1651-
this._collectVariables(variables, matcher.filePrefix);
1652+
if (Types.isString(matcher.filePrefix)) {
1653+
this._collectVariables(variables, matcher.filePrefix);
1654+
} else {
1655+
for (const fp of [...asArray(matcher.filePrefix.include || []), ...asArray(matcher.filePrefix.exclude || [])]) {
1656+
this._collectVariables(variables, fp);
1657+
}
1658+
}
16521659
}
16531660
});
16541661
}
@@ -1710,7 +1717,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
17101717
copy.uriProvider = taskSystemInfo.uriProvider;
17111718
}
17121719
if (hasFilePrefix) {
1713-
copy.filePrefix = await this._resolveVariable(resolver, copy.filePrefix);
1720+
const filePrefix = copy.filePrefix;
1721+
if (Types.isString(filePrefix)) {
1722+
copy.filePrefix = await this._resolveVariable(resolver, filePrefix);
1723+
} else if (filePrefix !== undefined) {
1724+
if (filePrefix.include) {
1725+
filePrefix.include = Array.isArray(filePrefix.include)
1726+
? await Promise.all(filePrefix.include.map(x => this._resolveVariable(resolver, x)))
1727+
: await this._resolveVariable(resolver, filePrefix.include);
1728+
}
1729+
if (filePrefix.exclude) {
1730+
filePrefix.exclude = Array.isArray(filePrefix.exclude)
1731+
? await Promise.all(filePrefix.exclude.map(x => this._resolveVariable(resolver, x)))
1732+
: await this._resolveVariable(resolver, filePrefix.exclude);
1733+
}
1734+
}
17141735
}
17151736
result.push(copy);
17161737
}

src/vs/workbench/contrib/tasks/common/problemMatcher.ts

Lines changed: 128 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ import { URI } from 'vs/base/common/uri';
1717
import { IJSONSchema } from 'vs/base/common/jsonSchema';
1818
import { ValidationStatus, ValidationState, IProblemReporter, Parser } from 'vs/base/common/parsers';
1919
import { IStringDictionary } from 'vs/base/common/collections';
20+
import { asArray } from 'vs/base/common/arrays';
21+
import { Schemas as NetworkSchemas } from 'vs/base/common/network';
2022

2123
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
2224
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
2325
import { Event, Emitter } from 'vs/base/common/event';
24-
import { IFileService, IFileStatWithPartialMetadata } from 'vs/platform/files/common/files';
26+
import { FileType, IFileService, IFileStatWithPartialMetadata, IFileSystemProvider } from 'vs/platform/files/common/files';
2527

2628
export enum FileLocationKind {
2729
Default,
2830
Relative,
2931
Absolute,
30-
AutoDetect
32+
AutoDetect,
33+
Search
3134
}
3235

3336
export module FileLocationKind {
@@ -39,6 +42,8 @@ export module FileLocationKind {
3942
return FileLocationKind.Relative;
4043
} else if (value === 'autodetect') {
4144
return FileLocationKind.AutoDetect;
45+
} else if (value === 'search') {
46+
return FileLocationKind.Search;
4247
} else {
4348
return undefined;
4449
}
@@ -132,7 +137,7 @@ export interface ProblemMatcher {
132137
source?: string;
133138
applyTo: ApplyToKind;
134139
fileLocation: FileLocationKind;
135-
filePrefix?: string;
140+
filePrefix?: string | Config.SearchFileLocationArgs;
136141
pattern: IProblemPattern | IProblemPattern[];
137142
severity?: Severity;
138143
watching?: IWatchingMatcher;
@@ -192,7 +197,7 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil
192197
let fullPath: string | undefined;
193198
if (kind === FileLocationKind.Absolute) {
194199
fullPath = filename;
195-
} else if ((kind === FileLocationKind.Relative) && matcher.filePrefix) {
200+
} else if ((kind === FileLocationKind.Relative) && matcher.filePrefix && Types.isString(matcher.filePrefix)) {
196201
fullPath = join(matcher.filePrefix, filename);
197202
} else if (kind === FileLocationKind.AutoDetect) {
198203
const matcherClone = Objects.deepClone(matcher);
@@ -212,6 +217,18 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil
212217

213218
matcherClone.fileLocation = FileLocationKind.Absolute;
214219
return getResource(filename, matcherClone);
220+
} else if (kind === FileLocationKind.Search && fileService) {
221+
const fsProvider = fileService.getProvider(NetworkSchemas.file);
222+
if (fsProvider) {
223+
const uri = await searchForFileLocation(filename, fsProvider, matcher.filePrefix as Config.SearchFileLocationArgs);
224+
fullPath = uri?.path;
225+
}
226+
227+
if (!fullPath) {
228+
const absoluteMatcher = Objects.deepClone(matcher);
229+
absoluteMatcher.fileLocation = FileLocationKind.Absolute;
230+
return getResource(filename, absoluteMatcher);
231+
}
215232
}
216233
if (fullPath === undefined) {
217234
throw new Error('FileLocationKind is not actionable. Does the matcher have a filePrefix? This should never happen.');
@@ -228,6 +245,56 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil
228245
}
229246
}
230247

248+
async function searchForFileLocation(filename: string, fsProvider: IFileSystemProvider, args: Config.SearchFileLocationArgs): Promise<URI | undefined> {
249+
const exclusions = new Set(asArray(args.exclude || []).map(x => URI.file(x).path));
250+
async function search(dir: URI): Promise<URI | undefined> {
251+
if (exclusions.has(dir.path)) {
252+
return undefined;
253+
}
254+
255+
const entries = await fsProvider.readdir(dir);
256+
const subdirs: URI[] = [];
257+
258+
for (const [name, fileType] of entries) {
259+
if (fileType === FileType.Directory) {
260+
subdirs.push(URI.joinPath(dir, name));
261+
continue;
262+
}
263+
264+
if (fileType === FileType.File) {
265+
/**
266+
* Note that sometimes the given `filename` could be a relative
267+
* path (not just the "name.ext" part). For example, the
268+
* `filename` can be "/subdir/name.ext". So, just comparing
269+
* `name` as `filename` is not sufficient. The workaround here
270+
* is to form the URI with `dir` and `name` and check if it ends
271+
* with the given `filename`.
272+
*/
273+
const fullUri = URI.joinPath(dir, name);
274+
if (fullUri.path.endsWith(filename)) {
275+
return fullUri;
276+
}
277+
}
278+
}
279+
280+
for (const subdir of subdirs) {
281+
const result = await search(subdir);
282+
if (result) {
283+
return result;
284+
}
285+
}
286+
return undefined;
287+
}
288+
289+
for (const dir of asArray(args.include || [])) {
290+
const hit = await search(URI.file(dir));
291+
if (hit) {
292+
return hit;
293+
}
294+
}
295+
return undefined;
296+
}
297+
231298
export interface ILineMatcher {
232299
matchLength: number;
233300
next(line: string): IProblemMatch | null;
@@ -796,8 +863,14 @@ export namespace Config {
796863
* - ["autodetect", "path value"]: the filename is treated
797864
* relative to the given path value, and if it does not
798865
* exist, it is treated as absolute.
866+
* - ["search", { include?: "" | []; exclude?: "" | [] }]: The filename
867+
* needs to be searched under the directories named by the "include"
868+
* property and their nested subdirectories. With "exclude" property
869+
* present, the directories should be removed from the search. When
870+
* `include` is not unprovided, the current workspace directory should
871+
* be used as the default.
799872
*/
800-
fileLocation?: string | string[];
873+
fileLocation?: string | string[] | ['search', SearchFileLocationArgs];
801874

802875
/**
803876
* The name of a predefined problem pattern, the inline definition
@@ -824,6 +897,11 @@ export namespace Config {
824897
background?: IBackgroundMonitor;
825898
}
826899

900+
export type SearchFileLocationArgs = {
901+
include?: string | string[];
902+
exclude?: string | string[];
903+
};
904+
827905
export type ProblemMatcherType = string | ProblemMatcher | Array<string | ProblemMatcher>;
828906

829907
export interface INamedProblemMatcher extends ProblemMatcher {
@@ -1367,7 +1445,7 @@ export class ProblemMatcherParser extends Parser {
13671445
applyTo = ApplyToKind.allDocuments;
13681446
}
13691447
let fileLocation: FileLocationKind | undefined = undefined;
1370-
let filePrefix: string | undefined = undefined;
1448+
let filePrefix: string | Config.SearchFileLocationArgs | undefined = undefined;
13711449

13721450
let kind: FileLocationKind | undefined;
13731451
if (Types.isUndefined(description.fileLocation)) {
@@ -1379,6 +1457,8 @@ export class ProblemMatcherParser extends Parser {
13791457
fileLocation = kind;
13801458
if ((kind === FileLocationKind.Relative) || (kind === FileLocationKind.AutoDetect)) {
13811459
filePrefix = '${workspaceFolder}';
1460+
} else if (kind === FileLocationKind.Search) {
1461+
filePrefix = { include: ['${workspaceFolder}'] };
13821462
}
13831463
}
13841464
} else if (Types.isStringArray(description.fileLocation)) {
@@ -1392,6 +1472,12 @@ export class ProblemMatcherParser extends Parser {
13921472
filePrefix = values[1];
13931473
}
13941474
}
1475+
} else if (Array.isArray(description.fileLocation)) {
1476+
const kind = FileLocationKind.fromString(description.fileLocation[0]);
1477+
if (kind === FileLocationKind.Search) {
1478+
fileLocation = FileLocationKind.Search;
1479+
filePrefix = description.fileLocation[1] ?? { include: ['${workspaceFolder}'] };
1480+
}
13951481
}
13961482

13971483
const pattern = description.pattern ? this.createProblemPattern(description.pattern) : undefined;
@@ -1605,16 +1691,49 @@ export namespace Schemas {
16051691
oneOf: [
16061692
{
16071693
type: 'string',
1608-
enum: ['absolute', 'relative', 'autoDetect']
1694+
enum: ['absolute', 'relative', 'autoDetect', 'search']
16091695
},
16101696
{
16111697
type: 'array',
16121698
items: {
16131699
type: 'string'
1614-
}
1700+
},
1701+
examples: [
1702+
['relative', '${workspaceFolder}'],
1703+
['autoDetect', '${workspaceFolder}'],
1704+
]
1705+
},
1706+
{
1707+
type: 'array',
1708+
items: [
1709+
{ type: 'string', enum: ['search'] },
1710+
{
1711+
type: 'object',
1712+
properties: {
1713+
'include': {
1714+
oneOf: [
1715+
{ type: 'string' },
1716+
{ type: 'array', items: { type: 'string' } }
1717+
]
1718+
},
1719+
'exclude': {
1720+
oneOf: [
1721+
{ type: 'string' },
1722+
{ type: 'array', items: { type: 'string' } }
1723+
]
1724+
},
1725+
},
1726+
required: ['include']
1727+
}
1728+
],
1729+
additionalItems: false,
1730+
examples: [
1731+
['search', { 'include': ['${workspaceFolder}'] }],
1732+
['search', { 'include': ['${workspaceFolder}'], 'exclude': [] }]
1733+
],
16151734
}
16161735
],
1617-
description: localize('ProblemMatcherSchema.fileLocation', 'Defines how file names reported in a problem pattern should be interpreted. A relative fileLocation may be an array, where the second element of the array is the path the relative file location.')
1736+
description: localize('ProblemMatcherSchema.fileLocation', 'Defines how file names reported in a problem pattern should be interpreted. A relative fileLocation may be an array, where the second element of the array is the path of the relative file location. The search fileLocation mode, performs a deep (and, possibly, heavy) file system search within the directories specified by the include/exclude properties of the second element (or the current workspace directory if not specified).')
16181737
},
16191738
background: {
16201739
type: 'object',

0 commit comments

Comments
 (0)