Skip to content

Commit

Permalink
FindTextInFiles doesn't support comma-separated globs with {}
Browse files Browse the repository at this point in the history
Fixes #169422
  • Loading branch information
andreamah committed Dec 22, 2022
1 parent 1b4af18 commit 1db9342
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 2 deletions.
87 changes: 85 additions & 2 deletions src/vs/workbench/services/search/common/queryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { IWorkspaceContextService, IWorkspaceFolderData, toWorkspaceFolder, Work
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/workbench/services/search/common/search';

/**
* One folder to search and a glob expression that should be applied.
*/
Expand Down Expand Up @@ -164,7 +163,7 @@ export class QueryBuilder {
pattern = Array.isArray(pattern) ? pattern.map(normalizeSlashes) : normalizeSlashes(pattern);
return expandPatterns
? this.parseSearchPaths(pattern)
: { pattern: patternListToIExpression(...(Array.isArray(pattern) ? pattern : [pattern])) };
: this.parseSearchPathWithBraceExpansion(pattern);
}

private commonQuery(folderResources: (IWorkspaceFolderData | URI)[] = [], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps<uri> {
Expand Down Expand Up @@ -279,6 +278,90 @@ export class QueryBuilder {
return !!contentPattern.isMultiline;
}

/**
* get first `{` and `}` curly braces that aren't escaped
* - if the brace is prepended by a \ character, then the next character is escaped
*/
private getStartEndInfo(pattern: string): { start: number; end: number; escapedString: string } {
let start = -1;
let inBraces = false;
let escaped = false;
let escapedString = '';
for (let i = 0; i < pattern.length; i++) {
const char = pattern[i];
if (escaped) {
escaped = false;
// if we escaped the brace, discard of it, otherwise, keep the escape character
escapedString += (char === '{' || char === '}') ? char : ('\\' + char);
continue;
}
switch (char) {
case '\\':
escaped = true;
break;
case '{':
escapedString += char;
inBraces = true;
start = escapedString.length - 1;
break;
case '}':
escapedString += char;
if (inBraces) {
return { start, end: escapedString.length - 1, escapedString: escapedString + pattern.substring(i + 1) };
}
break;
default:
escapedString += char;
break;
}
}

return { start, end: -1, escapedString };
}
/**
* parses out curly braces and returns equivalent globs
* @param pattern
* @returns
*/
private parseOutBraces(pattern: string): string[] {
const { start, end, escapedString } = this.getStartEndInfo(pattern);
if (start === -1 || end === -1) {
return [escapedString];
}

const strInBraces = escapedString.substring(start + 1, end);
const fixedStart = escapedString.substring(0, start);
const fixedEnd = escapedString.substring(end + 1);

const arr = glob.splitGlobAware(strInBraces, ',');

const ends = this.parseOutBraces(fixedEnd);

return arr.flatMap((elem) => {
const start = fixedStart + elem;
return ends.map((end) => {
return start + end;
});
});
}

/**
* parse out the FIRST-LEVEL curly braces from glob
* is aware of escaping, but no other nuances
* @param pattern
* @returns
*/
parseSearchPathWithBraceExpansion(pattern: string | string[]): ISearchPathsInfo {
if (!Array.isArray(pattern)) {
pattern = [pattern];
}
const patterns = pattern.flatMap((currentPattern) => {
return this.parseOutBraces(currentPattern);
});

return { pattern: patternListToIExpression(...(patterns)) };
}

/**
* Take the includePattern as seen in the search viewlet, and split into components that look like searchPaths, and
* glob patterns. Glob patterns are expanded from 'foo/bar' to '{foo/bar/**, **\/foo/bar}.
Expand Down
21 changes: 21 additions & 0 deletions src/vs/workbench/services/search/test/browser/queryBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,27 @@ suite('QueryBuilder', () => {
});
});

suite('parseSearchPathWithBraceExpansion', () => {
test('simple includes', () => {
function testSimpleIncludes(includePattern: string, expectedPatterns: string[]): void {
const result = queryBuilder.parseSearchPathWithBraceExpansion(includePattern);
assert.deepStrictEqual(
{ ...result.pattern },
patternsToIExpression(...expectedPatterns),
includePattern);
assert.strictEqual(result.searchPaths, undefined);
}

[
['eep/{a,b}/test', ['eep/a/test', 'eep/b/test']],
['eep/{a,b}/{c,d,e}', ['eep/a/c', 'eep/a/d', 'eep/a/e', 'eep/b/c', 'eep/b/d', 'eep/b/e']],
['eep/{a,b}/\\{c,d,e}', ['eep/a/{c,d,e}', 'eep/b/{c,d,e}']],
['eep/{a,b\\}/test', ['eep/{a,b}/test']],
['eep/{a,\\b}/test', ['eep/\\b/test', 'eep/a/test']],
].forEach(([includePattern, expectedPatterns]) => testSimpleIncludes(<string>includePattern, <string[]>expectedPatterns));
});
});

suite('parseSearchPaths', () => {
test('simple includes', () => {
function testSimpleIncludes(includePattern: string, expectedPatterns: string[]): void {
Expand Down

0 comments on commit 1db9342

Please sign in to comment.