@@ -17,17 +17,20 @@ import { URI } from 'vs/base/common/uri';
17
17
import { IJSONSchema } from 'vs/base/common/jsonSchema' ;
18
18
import { ValidationStatus , ValidationState , IProblemReporter , Parser } from 'vs/base/common/parsers' ;
19
19
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' ;
20
22
21
23
import { IMarkerData , MarkerSeverity } from 'vs/platform/markers/common/markers' ;
22
24
import { ExtensionsRegistry , ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry' ;
23
25
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' ;
25
27
26
28
export enum FileLocationKind {
27
29
Default ,
28
30
Relative ,
29
31
Absolute ,
30
- AutoDetect
32
+ AutoDetect ,
33
+ Search
31
34
}
32
35
33
36
export module FileLocationKind {
@@ -39,6 +42,8 @@ export module FileLocationKind {
39
42
return FileLocationKind . Relative ;
40
43
} else if ( value === 'autodetect' ) {
41
44
return FileLocationKind . AutoDetect ;
45
+ } else if ( value === 'search' ) {
46
+ return FileLocationKind . Search ;
42
47
} else {
43
48
return undefined ;
44
49
}
@@ -132,7 +137,7 @@ export interface ProblemMatcher {
132
137
source ?: string ;
133
138
applyTo : ApplyToKind ;
134
139
fileLocation : FileLocationKind ;
135
- filePrefix ?: string ;
140
+ filePrefix ?: string | Config . SearchFileLocationArgs ;
136
141
pattern : IProblemPattern | IProblemPattern [ ] ;
137
142
severity ?: Severity ;
138
143
watching ?: IWatchingMatcher ;
@@ -192,7 +197,7 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil
192
197
let fullPath : string | undefined ;
193
198
if ( kind === FileLocationKind . Absolute ) {
194
199
fullPath = filename ;
195
- } else if ( ( kind === FileLocationKind . Relative ) && matcher . filePrefix ) {
200
+ } else if ( ( kind === FileLocationKind . Relative ) && matcher . filePrefix && Types . isString ( matcher . filePrefix ) ) {
196
201
fullPath = join ( matcher . filePrefix , filename ) ;
197
202
} else if ( kind === FileLocationKind . AutoDetect ) {
198
203
const matcherClone = Objects . deepClone ( matcher ) ;
@@ -212,6 +217,18 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil
212
217
213
218
matcherClone . fileLocation = FileLocationKind . Absolute ;
214
219
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
+ }
215
232
}
216
233
if ( fullPath === undefined ) {
217
234
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
228
245
}
229
246
}
230
247
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
+
231
298
export interface ILineMatcher {
232
299
matchLength : number ;
233
300
next ( line : string ) : IProblemMatch | null ;
@@ -796,8 +863,14 @@ export namespace Config {
796
863
* - ["autodetect", "path value"]: the filename is treated
797
864
* relative to the given path value, and if it does not
798
865
* 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.
799
872
*/
800
- fileLocation ?: string | string [ ] ;
873
+ fileLocation ?: string | string [ ] | [ 'search' , SearchFileLocationArgs ] ;
801
874
802
875
/**
803
876
* The name of a predefined problem pattern, the inline definition
@@ -824,6 +897,11 @@ export namespace Config {
824
897
background ?: IBackgroundMonitor ;
825
898
}
826
899
900
+ export type SearchFileLocationArgs = {
901
+ include ?: string | string [ ] ;
902
+ exclude ?: string | string [ ] ;
903
+ } ;
904
+
827
905
export type ProblemMatcherType = string | ProblemMatcher | Array < string | ProblemMatcher > ;
828
906
829
907
export interface INamedProblemMatcher extends ProblemMatcher {
@@ -1367,7 +1445,7 @@ export class ProblemMatcherParser extends Parser {
1367
1445
applyTo = ApplyToKind . allDocuments ;
1368
1446
}
1369
1447
let fileLocation : FileLocationKind | undefined = undefined ;
1370
- let filePrefix : string | undefined = undefined ;
1448
+ let filePrefix : string | Config . SearchFileLocationArgs | undefined = undefined ;
1371
1449
1372
1450
let kind : FileLocationKind | undefined ;
1373
1451
if ( Types . isUndefined ( description . fileLocation ) ) {
@@ -1379,6 +1457,8 @@ export class ProblemMatcherParser extends Parser {
1379
1457
fileLocation = kind ;
1380
1458
if ( ( kind === FileLocationKind . Relative ) || ( kind === FileLocationKind . AutoDetect ) ) {
1381
1459
filePrefix = '${workspaceFolder}' ;
1460
+ } else if ( kind === FileLocationKind . Search ) {
1461
+ filePrefix = { include : [ '${workspaceFolder}' ] } ;
1382
1462
}
1383
1463
}
1384
1464
} else if ( Types . isStringArray ( description . fileLocation ) ) {
@@ -1392,6 +1472,12 @@ export class ProblemMatcherParser extends Parser {
1392
1472
filePrefix = values [ 1 ] ;
1393
1473
}
1394
1474
}
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
+ }
1395
1481
}
1396
1482
1397
1483
const pattern = description . pattern ? this . createProblemPattern ( description . pattern ) : undefined ;
@@ -1605,16 +1691,49 @@ export namespace Schemas {
1605
1691
oneOf : [
1606
1692
{
1607
1693
type : 'string' ,
1608
- enum : [ 'absolute' , 'relative' , 'autoDetect' ]
1694
+ enum : [ 'absolute' , 'relative' , 'autoDetect' , 'search' ]
1609
1695
} ,
1610
1696
{
1611
1697
type : 'array' ,
1612
1698
items : {
1613
1699
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
+ ] ,
1615
1734
}
1616
1735
] ,
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) .' )
1618
1737
} ,
1619
1738
background : {
1620
1739
type : 'object' ,
0 commit comments