diff --git a/CHANGELOG.md b/CHANGELOG.md index f262753da048b..073e9d73675d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## V1.13.0 +- [search-in-workspace] Improve include/exclude search in workspace [#9307](https://github.com/eclipse-theia/theia/pull/9307) + +[Breaking Changes:](#breaking_changes_1.13.0) + +- [search-in-workspace] `RipgrepSearchInWorkspaceServer.getArgs` added new parameter `rootPaths: string[]` [#9307](https://github.com/eclipse-theia/theia/pull/9307) + ## v1.12.0 - 3/25/2020 [1.12.0 Milestone](https://github.com/eclipse-theia/theia/milestone/17) diff --git a/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.slow-spec.ts b/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.slow-spec.ts index f12152c6c6ec7..4fabb1934d98a 100644 --- a/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.slow-spec.ts +++ b/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.slow-spec.ts @@ -931,35 +931,35 @@ describe('ripgrep-search-in-workspace-server', function (): void { }); }); -describe('ripgrep-search-in-workspace-server-matchPatternToPathResult', function (): void { +describe('#resolvePatternToPathMap', function (): void { this.timeout(10000); - it('Resolve pattern to path for filename', function (): void { + it('should resolve pattern to path for filename', function (): void { const pattern = 'carrots'; - matchPatternToPathResult(pattern, path.join(rootDirA, pattern)); + checkResolvedPathForPattern(pattern, path.join(rootDirA, pattern)); }); - it('Resolve pattern to path for relative filename', function (): void { + it('should resolve pattern to path for relative filename', function (): void { const filename = 'carrots'; const pattern = `./${filename}`; - matchPatternToPathResult(pattern, path.join(rootDirA, filename)); + checkResolvedPathForPattern(pattern, path.join(rootDirA, filename)); }); - it('Resolve relative pattern with sub-folders glob', function (): void { + it('should resolve relative pattern with sub-folders glob', function (): void { const filename = 'carrots'; const pattern = `./${filename}/**`; - matchPatternToPathResult(pattern, path.join(rootDirA, filename)); + checkResolvedPathForPattern(pattern, path.join(rootDirA, filename)); }); - it('Resolve absolute path pattern', function (): void { + it('should resolve absolute path pattern', function (): void { const pattern = `${rootDirA}/carrots`; - matchPatternToPathResult(pattern, pattern); + checkResolvedPathForPattern(pattern, pattern); }); }); -describe('ripgrep-search-in-workspace-server-filePatternToGlobs', function (): void { +describe('#patternToGlobCLIArguments', function (): void { this.timeout(10000); - it('Resolve path to glob - filename', function (): void { + it('should resolve path to glob - filename', function (): void { [true, false].forEach(excludeFlag => { const excludePrefix = excludeFlag ? '!' : ''; const filename = 'carrots'; @@ -968,12 +968,12 @@ describe('ripgrep-search-in-workspace-server-filePatternToGlobs', function (): v `--glob=${excludePrefix}**/${filename}/*` ]; - const actual = ripgrepServer['filePatternToGlobs'](filename, rootDirA, excludeFlag); + const actual = ripgrepServer['patternToGlobCLIArguments'](filename, excludeFlag); expect(expected).to.have.deep.members(actual); }); }); - it('Resolve path to glob - glob prefixed folder', function (): void { + it('should resolve path to glob - glob prefixed folder', function (): void { [true, false].forEach(excludeFlag => { const excludePrefix = excludeFlag ? '!' : ''; const filename = 'carrots'; @@ -983,12 +983,12 @@ describe('ripgrep-search-in-workspace-server-filePatternToGlobs', function (): v `--glob=${excludePrefix}**/${filename}/*` ]; - const actual = ripgrepServer['filePatternToGlobs'](inputPath, rootDirA, excludeFlag); + const actual = ripgrepServer['patternToGlobCLIArguments'](inputPath, excludeFlag); expect(expected).to.have.deep.members(actual); }); }); - it('Resolve path to glob - path segment', function (): void { + it('should resolve path to glob - path segment', function (): void { [true, false].forEach(excludeFlag => { const excludePrefix = excludeFlag ? '!' : ''; const filename = 'carrots'; @@ -998,12 +998,12 @@ describe('ripgrep-search-in-workspace-server-filePatternToGlobs', function (): v `--glob=${excludePrefix}**/${filename}/*` ]; - const actual = ripgrepServer['filePatternToGlobs'](inputPath, rootDirA, excludeFlag); + const actual = ripgrepServer['patternToGlobCLIArguments'](inputPath, excludeFlag); expect(expected).to.have.deep.members(actual); }); }); - it('Resolve path to glob - already a glob', function (): void { + it('should resolve path to glob - already a glob', function (): void { [true, false].forEach(excludeFlag => { const excludePrefix = excludeFlag ? '!' : ''; const filename = 'carrots'; @@ -1012,12 +1012,12 @@ describe('ripgrep-search-in-workspace-server-filePatternToGlobs', function (): v `--glob=${excludePrefix}**/${filename}/**/*`, ]; - const actual = ripgrepServer['filePatternToGlobs'](inputPath, rootDirA, excludeFlag); + const actual = ripgrepServer['patternToGlobCLIArguments'](inputPath, excludeFlag); expect(expected).to.have.deep.members(actual); }); }); - it('Resolve path to glob - path segment glob suffixed', function (): void { + it('should resolve path to glob - path segment glob suffixed', function (): void { [true, false].forEach(excludeFlag => { const excludePrefix = excludeFlag ? '!' : ''; const filename = 'carrots'; @@ -1026,13 +1026,13 @@ describe('ripgrep-search-in-workspace-server-filePatternToGlobs', function (): v `--glob=${excludePrefix}**/${filename}/**/*`, ]; - const actual = ripgrepServer['filePatternToGlobs'](inputPath, rootDirA, excludeFlag); + const actual = ripgrepServer['patternToGlobCLIArguments'](inputPath, excludeFlag); expect(expected).to.have.deep.members(actual); }); }); }); -function matchPatternToPathResult(pattern: string, expectedPath: string): void { - const resultMap: Map = ripgrepServer['resolvePatternToPathMap']([pattern], [rootDirA]); - expect(resultMap.get(pattern)).equal(expectedPath); +function checkResolvedPathForPattern(pattern: string, expectedPath: string): void { + const resultMap: Map = ripgrepServer['resolvePatternToPathsMap']([pattern], [rootDirA]); + expect(resultMap.get(pattern)![0]).equal(expectedPath); } diff --git a/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.ts b/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.ts index 3267a5bf6172b..ba74fb0a38b3a 100644 --- a/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.ts +++ b/packages/search-in-workspace/src/node/ripgrep-search-in-workspace-server.ts @@ -39,14 +39,10 @@ function bytesOrTextToString(obj: IRgBytesOrText): string { } /** - * Attempts to build an validate an absolute path from a given pattern an a root folder + * Attempts to build an validate an absolute path from a given pattern and a root folder * If a first pass is not successful a second pass will consider finding it by removing * a glob suffix '/**', if a valid path is successfully built the suffix can be appended * back to the result string i.e. depending on the keepSuffix flag. - * - * @param root - * @param pattern - * @param keepSuffix */ function findPathInSuffixedPattern(root: string, pattern: string, keepSuffix: boolean): string | undefined { let suffix = undefined; @@ -56,7 +52,7 @@ function findPathInSuffixedPattern(root: string, pattern: string, keepSuffix: bo return searchPath; } - // Attempt to find a path in provided pattern but without a glob suffix + // Attempt to find a path in provided pattern but without a glob suffix. let patternBase = undefined; ({ patternBase, suffix } = stripPatternGlobSuffix(pattern)); if (suffix) { @@ -83,7 +79,6 @@ function findPathInPattern(root: string, pattern: string): string | undefined { * Returns the original pattern if not successful. * * Returns the pattern with out the glob stars prefix '/**' or without '\\**' - * @param pattern */ function stripPatternGlobSuffix(pattern: string): { patternBase: string, suffix: string | undefined } { let patternBase: string = pattern; @@ -160,12 +155,10 @@ export class RipgrepSearchInWorkspaceServer implements SearchInWorkspaceServer { const args = new Set(); const appendGlobArgs = (rawPatterns: string[], exclude: boolean) => { - rootPaths.forEach(root => { - for (const rawPattern of rawPatterns) { - if (rawPattern !== '') { - const globArguments = this.filePatternToGlobs(rawPattern, root, exclude); - globArguments.forEach(arg => args.add(arg)); - } + rawPatterns.forEach(rawPattern => { + if (rawPattern !== '') { + const globArguments = this.patternToGlobCLIArguments(rawPattern, exclude); + globArguments.forEach(arg => args.add(arg)); } }); }; @@ -202,15 +195,10 @@ export class RipgrepSearchInWorkspaceServer implements SearchInWorkspaceServer { } /** - * Transforms a file pattern to a glob pattern (unless it's already a glob). - * It then creates corresponding 'ripgrep' CLI parameters, - * - * @param rawPattern - * @param rootFolder - * @param exclude + * Transforms a given file pattern to 'ripgrep' glob CLI arguments. */ - protected filePatternToGlobs(pattern: string, rootFolder: string, exclude: boolean): string[] { - const prefixPattern = (inputPattern: string): string => { + protected patternToGlobCLIArguments(pattern: string, exclude: boolean): string[] { + const toCLIGlobArgument = (inputPattern: string): string => { const globCommandArgument = '--glob='; const excludeChar = exclude ? '!' : ''; const subDirGlobPattern = '**/'; @@ -222,16 +210,16 @@ export class RipgrepSearchInWorkspaceServer implements SearchInWorkspaceServer { return `${globCommandArgument}${excludeChar}${updatedPattern}`; }; - const prefixedPattern = prefixPattern(pattern); + const globArgument = toCLIGlobArgument(pattern); - const globs = [prefixedPattern]; - if (!prefixedPattern.endsWith('*')) { - // Add a generic glob entry to include files inside a given directory. - const suffix = prefixedPattern.endsWith('/') ? '*' : '/*'; - globs.push(`${prefixedPattern}${suffix}`); + const globArgumentsArray = [globArgument]; + if (!globArgument.endsWith('*')) { + // Add a generic glob CLI argument entry to include files inside a given directory. + const suffix = globArgument.endsWith('/') ? '*' : '/*'; + globArgumentsArray.push(`${globArgument}${suffix}`); } - return globs; + return globArgumentsArray; }; /** @@ -245,10 +233,6 @@ export class RipgrepSearchInWorkspaceServer implements SearchInWorkspaceServer { * the search directories to the ones provided as includes. * Relative paths are allowed, the application will attempt to translate them to valid absolute paths * based on the applicable search directories. - * - * @param what - * @param rootUris - * @param opts */ search(what: string, rootUris: string[], opts?: SearchInWorkspaceOptions): Promise { // Start the rg process. Use --vimgrep to get one result per @@ -271,7 +255,7 @@ export class RipgrepSearchInWorkspaceServer implements SearchInWorkspaceServer { } } - const args = [...rgArgs, what].concat(searchPaths); + const args = [...rgArgs, what, ...searchPaths]; const processOptions: RawProcessOptions = { command: this.rgPath, args @@ -418,19 +402,18 @@ export class RipgrepSearchInWorkspaceServer implements SearchInWorkspaceServer { /** * Transform exclude option patterns from relative paths to absolute paths as long * as the resulting paths get validated. - * - * @param opts */ protected adjustExcludePaths(rootPaths: string[], opts: SearchInWorkspaceOptions | undefined): void { if (!opts || !opts.exclude) { return; } - const patternToPathMap: Map = this.resolvePatternToPathMap(opts.exclude, rootPaths, true); + const patternToPathsMap: Map = this.resolvePatternToPathsMap(opts.exclude, rootPaths, true); const updatedExcludes: string[] = []; opts.exclude.forEach(pattern => { - const adjustedPattern = patternToPathMap.get(pattern) ? patternToPathMap.get(pattern) as string : pattern; - updatedExcludes.push(adjustedPattern); + const maybePath = patternToPathsMap.get(pattern); + const adjustedPatterns = maybePath ? maybePath : [pattern]; + updatedExcludes.push(...adjustedPatterns); }); opts.exclude = updatedExcludes; } @@ -446,41 +429,38 @@ export class RipgrepSearchInWorkspaceServer implements SearchInWorkspaceServer { * * Any pattern that resulted in a valid search path will be removed from the 'include' list as it is * provided as an equivalent search path instead. - * - * @param rootPaths - Workspace root paths - * @param opts */ protected resolveSearchPathsFromIncludes(rootPaths: string[], opts: SearchInWorkspaceOptions | undefined): string[] { if (!opts || !opts.include) { return rootPaths; } - const includesAsPaths = this.resolvePatternToPathMap(opts.include, rootPaths); + const includesAsPaths = this.resolvePatternToPathsMap(opts.include, rootPaths); const patternPaths = Array.from(includesAsPaths.keys()); // Exclude include file patters that were successfully translated to search paths opts.include = opts.include.filter(item => !patternPaths.includes(item)); - return includesAsPaths.size > 0 ? Array.from(includesAsPaths.values()) : rootPaths; + + return includesAsPaths.size > 0 ? [].concat.apply([], Array.from(includesAsPaths.values())) : rootPaths; } /** * Attempts to resolve valid file paths from a given list of patterns. * The given (e.g. workspace) root paths are used to try resolving relative path patterns to an absolute path. - * The resulting map will include all patterns associated to its equivalent file path. + * The resulting map will include all patterns associated to its equivalent file paths. * The given patterns that are not successfully mapped to a file system path are not included. - * - * @param rootPaths - * @param patterns */ - protected resolvePatternToPathMap(patterns: string[], rootPaths: string[], keepSuffix: boolean = false): Map { - const patternToPathMap = new Map(); + protected resolvePatternToPathsMap(patterns: string[], rootPaths: string[], keepSuffix: boolean = false): Map { + const patternToPathMap = new Map(); patterns.forEach(pattern => { rootPaths.forEach(root => { const foundPath = findPathInSuffixedPattern(root, pattern, keepSuffix); if (foundPath) { - patternToPathMap.set(pattern, foundPath); + let pathArray = patternToPathMap.get(pattern); + pathArray = pathArray ? [...pathArray, foundPath] : [foundPath]; + patternToPathMap.set(pattern, pathArray); } }); });