diff options
author | Babak K. Shandiz <babak.k.shandiz@gmail.com> | 2022-11-10 02:50:24 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-10 02:50:24 +0300 |
commit | 7fc3b1fbda9e3bde2049ac1aa48a8133984b2c47 (patch) | |
tree | 8fe16548e097d7a41e652b62a6228b9f08ad40b0 | |
parent | 245c0c781c96f7f39b170b0090f16390071f407d (diff) |
🎁 Add `search` to file location methods in tasks (#165156)
-rw-r--r-- | src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts | 25 | ||||
-rw-r--r-- | src/vs/workbench/contrib/tasks/common/problemMatcher.ts | 137 |
2 files changed, 151 insertions, 11 deletions
diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 0a35133ca7d..6e004ffba4d 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -16,6 +16,7 @@ import * as resources from 'vs/base/common/resources'; import Severity from 'vs/base/common/severity'; import * as Types from 'vs/base/common/types'; import * as nls from 'vs/nls'; +import { asArray } from 'vs/base/common/arrays'; import { IModelService } from 'vs/editor/common/services/model'; import { IFileService } from 'vs/platform/files/common/files'; @@ -1648,7 +1649,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { matcher = value; } if (matcher && matcher.filePrefix) { - this._collectVariables(variables, matcher.filePrefix); + if (Types.isString(matcher.filePrefix)) { + this._collectVariables(variables, matcher.filePrefix); + } else { + for (const fp of [...asArray(matcher.filePrefix.include || []), ...asArray(matcher.filePrefix.exclude || [])]) { + this._collectVariables(variables, fp); + } + } } }); } @@ -1710,7 +1717,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { copy.uriProvider = taskSystemInfo.uriProvider; } if (hasFilePrefix) { - copy.filePrefix = await this._resolveVariable(resolver, copy.filePrefix); + const filePrefix = copy.filePrefix; + if (Types.isString(filePrefix)) { + copy.filePrefix = await this._resolveVariable(resolver, filePrefix); + } else if (filePrefix !== undefined) { + if (filePrefix.include) { + filePrefix.include = Array.isArray(filePrefix.include) + ? await Promise.all(filePrefix.include.map(x => this._resolveVariable(resolver, x))) + : await this._resolveVariable(resolver, filePrefix.include); + } + if (filePrefix.exclude) { + filePrefix.exclude = Array.isArray(filePrefix.exclude) + ? await Promise.all(filePrefix.exclude.map(x => this._resolveVariable(resolver, x))) + : await this._resolveVariable(resolver, filePrefix.exclude); + } + } } result.push(copy); } diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index de370e64892..f67b71187ed 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -17,17 +17,20 @@ import { URI } from 'vs/base/common/uri'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ValidationStatus, ValidationState, IProblemReporter, Parser } from 'vs/base/common/parsers'; import { IStringDictionary } from 'vs/base/common/collections'; +import { asArray } from 'vs/base/common/arrays'; +import { Schemas as NetworkSchemas } from 'vs/base/common/network'; 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, IFileStatWithPartialMetadata } from 'vs/platform/files/common/files'; +import { FileType, IFileService, IFileStatWithPartialMetadata, IFileSystemProvider } from 'vs/platform/files/common/files'; export enum FileLocationKind { Default, Relative, Absolute, - AutoDetect + AutoDetect, + Search } export module FileLocationKind { @@ -39,6 +42,8 @@ export module FileLocationKind { return FileLocationKind.Relative; } else if (value === 'autodetect') { return FileLocationKind.AutoDetect; + } else if (value === 'search') { + return FileLocationKind.Search; } else { return undefined; } @@ -132,7 +137,7 @@ export interface ProblemMatcher { source?: string; applyTo: ApplyToKind; fileLocation: FileLocationKind; - filePrefix?: string; + filePrefix?: string | Config.SearchFileLocationArgs; pattern: IProblemPattern | IProblemPattern[]; severity?: Severity; watching?: IWatchingMatcher; @@ -192,7 +197,7 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil let fullPath: string | undefined; if (kind === FileLocationKind.Absolute) { fullPath = filename; - } else if ((kind === FileLocationKind.Relative) && matcher.filePrefix) { + } else if ((kind === FileLocationKind.Relative) && matcher.filePrefix && Types.isString(matcher.filePrefix)) { fullPath = join(matcher.filePrefix, filename); } else if (kind === FileLocationKind.AutoDetect) { const matcherClone = Objects.deepClone(matcher); @@ -212,6 +217,18 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil matcherClone.fileLocation = FileLocationKind.Absolute; return getResource(filename, matcherClone); + } else if (kind === FileLocationKind.Search && fileService) { + const fsProvider = fileService.getProvider(NetworkSchemas.file); + if (fsProvider) { + const uri = await searchForFileLocation(filename, fsProvider, matcher.filePrefix as Config.SearchFileLocationArgs); + fullPath = uri?.path; + } + + if (!fullPath) { + const absoluteMatcher = Objects.deepClone(matcher); + absoluteMatcher.fileLocation = FileLocationKind.Absolute; + return getResource(filename, absoluteMatcher); + } } if (fullPath === undefined) { 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 } } +async function searchForFileLocation(filename: string, fsProvider: IFileSystemProvider, args: Config.SearchFileLocationArgs): Promise<URI | undefined> { + const exclusions = new Set(asArray(args.exclude || []).map(x => URI.file(x).path)); + async function search(dir: URI): Promise<URI | undefined> { + if (exclusions.has(dir.path)) { + return undefined; + } + + const entries = await fsProvider.readdir(dir); + const subdirs: URI[] = []; + + for (const [name, fileType] of entries) { + if (fileType === FileType.Directory) { + subdirs.push(URI.joinPath(dir, name)); + continue; + } + + if (fileType === FileType.File) { + /** + * Note that sometimes the given `filename` could be a relative + * path (not just the "name.ext" part). For example, the + * `filename` can be "/subdir/name.ext". So, just comparing + * `name` as `filename` is not sufficient. The workaround here + * is to form the URI with `dir` and `name` and check if it ends + * with the given `filename`. + */ + const fullUri = URI.joinPath(dir, name); + if (fullUri.path.endsWith(filename)) { + return fullUri; + } + } + } + + for (const subdir of subdirs) { + const result = await search(subdir); + if (result) { + return result; + } + } + return undefined; + } + + for (const dir of asArray(args.include || [])) { + const hit = await search(URI.file(dir)); + if (hit) { + return hit; + } + } + return undefined; +} + export interface ILineMatcher { matchLength: number; next(line: string): IProblemMatch | null; @@ -796,8 +863,14 @@ export namespace Config { * - ["autodetect", "path value"]: the filename is treated * relative to the given path value, and if it does not * exist, it is treated as absolute. + * - ["search", { include?: "" | []; exclude?: "" | [] }]: The filename + * needs to be searched under the directories named by the "include" + * property and their nested subdirectories. With "exclude" property + * present, the directories should be removed from the search. When + * `include` is not unprovided, the current workspace directory should + * be used as the default. */ - fileLocation?: string | string[]; + fileLocation?: string | string[] | ['search', SearchFileLocationArgs]; /** * The name of a predefined problem pattern, the inline definition @@ -824,6 +897,11 @@ export namespace Config { background?: IBackgroundMonitor; } + export type SearchFileLocationArgs = { + include?: string | string[]; + exclude?: string | string[]; + }; + export type ProblemMatcherType = string | ProblemMatcher | Array<string | ProblemMatcher>; export interface INamedProblemMatcher extends ProblemMatcher { @@ -1367,7 +1445,7 @@ export class ProblemMatcherParser extends Parser { applyTo = ApplyToKind.allDocuments; } let fileLocation: FileLocationKind | undefined = undefined; - let filePrefix: string | undefined = undefined; + let filePrefix: string | Config.SearchFileLocationArgs | undefined = undefined; let kind: FileLocationKind | undefined; if (Types.isUndefined(description.fileLocation)) { @@ -1379,6 +1457,8 @@ export class ProblemMatcherParser extends Parser { fileLocation = kind; if ((kind === FileLocationKind.Relative) || (kind === FileLocationKind.AutoDetect)) { filePrefix = '${workspaceFolder}'; + } else if (kind === FileLocationKind.Search) { + filePrefix = { include: ['${workspaceFolder}'] }; } } } else if (Types.isStringArray(description.fileLocation)) { @@ -1392,6 +1472,12 @@ export class ProblemMatcherParser extends Parser { filePrefix = values[1]; } } + } else if (Array.isArray(description.fileLocation)) { + const kind = FileLocationKind.fromString(description.fileLocation[0]); + if (kind === FileLocationKind.Search) { + fileLocation = FileLocationKind.Search; + filePrefix = description.fileLocation[1] ?? { include: ['${workspaceFolder}'] }; + } } const pattern = description.pattern ? this.createProblemPattern(description.pattern) : undefined; @@ -1605,16 +1691,49 @@ export namespace Schemas { oneOf: [ { type: 'string', - enum: ['absolute', 'relative', 'autoDetect'] + enum: ['absolute', 'relative', 'autoDetect', 'search'] }, { type: 'array', items: { type: 'string' - } + }, + examples: [ + ['relative', '${workspaceFolder}'], + ['autoDetect', '${workspaceFolder}'], + ] + }, + { + type: 'array', + items: [ + { type: 'string', enum: ['search'] }, + { + type: 'object', + properties: { + 'include': { + oneOf: [ + { type: 'string' }, + { type: 'array', items: { type: 'string' } } + ] + }, + 'exclude': { + oneOf: [ + { type: 'string' }, + { type: 'array', items: { type: 'string' } } + ] + }, + }, + required: ['include'] + } + ], + additionalItems: false, + examples: [ + ['search', { 'include': ['${workspaceFolder}'] }], + ['search', { 'include': ['${workspaceFolder}'], 'exclude': [] }] + ], } ], - 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.') + 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).') }, background: { type: 'object', |