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);
}
});
});