Skip to content

Commit

Permalink
Respect 'when' clauses in files.exclude - #19983
Browse files Browse the repository at this point in the history
  • Loading branch information
roblourens committed Mar 20, 2017
1 parent 288123a commit 188ef21
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 27 deletions.
66 changes: 47 additions & 19 deletions src/vs/base/common/glob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import strings = require('vs/base/common/strings');
import paths = require('vs/base/common/paths');
import { BoundedLinkedMap } from 'vs/base/common/map';
import { CharCode } from 'vs/base/common/charCode';
import { TPromise } from 'vs/base/common/winjs.base';

export interface IExpression {
[pattern: string]: boolean | SiblingClause | any;
Expand Down Expand Up @@ -218,22 +219,24 @@ const T4 = /^\*\*((\/[\w\.-]+)+)\/?$/; // **/something/else
const T5 = /^([\w\.-]+(\/[\w\.-]+)*)\/?$/; // something/else

export type ParsedPattern = (path: string, basename?: string) => boolean;
export type ParsedExpression = (path: string, basename?: string, siblingsFn?: () => string[]) => string /* the matching pattern */;

// The ParsedExpression returns a Promise iff siblingsFn returns a Promise.
export type ParsedExpression = (path: string, basename?: string, siblingsFn?: () => string[] | TPromise<string[]>) => string | TPromise<string> /* the matching pattern */;

export interface IGlobOptions {
trimForExclusions?: boolean;
}

interface ParsedStringPattern {
(path: string, basename: string): string /* the matching pattern */;
(path: string, basename: string): string | TPromise<string> /* the matching pattern */;
basenames?: string[];
patterns?: string[];
allBasenames?: string[];
allPaths?: string[];
}
type SiblingsPattern = { siblings: string[], name: string };
interface ParsedExpressionPattern {
(path: string, basename: string, siblingsPatternFn: () => SiblingsPattern): string /* the matching pattern */;
(path: string, basename: string, siblingsPatternFn: () => SiblingsPattern | TPromise<SiblingsPattern>): string | TPromise<string> /* the matching pattern */;
requiresSiblings?: boolean;
allBasenames?: string[];
allPaths?: string[];
Expand Down Expand Up @@ -427,6 +430,16 @@ export function parse(arg1: string | IExpression, options: IGlobOptions = {}): a
return parsedExpression(<IExpression>arg1, options);
}

/**
* Same as `parse`, but the ParsedExpression is guaranteed to return a Promise
*/
export function parseToAsync(expression: IExpression, options?: IGlobOptions): ParsedExpression {
const parsedExpression = parse(expression, options);
return (path: string, basename?: string, siblingsFn?: () => TPromise<string[]>): TPromise<string> => {
return TPromise.as(parsedExpression(path, basename, siblingsFn));
};
}

export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
return (<ParsedStringPattern>patternOrExpression).allBasenames || [];
}
Expand Down Expand Up @@ -475,23 +488,32 @@ function parsedExpression(expression: IExpression, options: IGlobOptions): Parse
return resultExpression;
}

const resultExpression: ParsedStringPattern = function (path: string, basename: string, siblingsFn?: () => string[]) {
let siblingsPattern: SiblingsPattern;
const resultExpression: ParsedStringPattern = function (path: string, basename: string, siblingsFn?: () => string[] | TPromise<string[]>) {
let siblingsPattern: SiblingsPattern | TPromise<SiblingsPattern>;
let siblingsResolved = !siblingsFn;

function siblingsToSiblingsPattern(siblings: string[]) {
if (siblings && siblings.length) {
if (!basename) {
basename = paths.basename(path);
}
const name = basename.substr(0, basename.length - paths.extname(path).length);
return { siblings, name };
}

return undefined;
}

function siblingsPatternFn() {
// Resolve siblings only once
if (!siblingsResolved) {
siblingsResolved = true;
const siblings = siblingsFn();
if (siblings && siblings.length) {
if (!basename) {
basename = paths.basename(path);
}
const name = basename.substr(0, basename.length - paths.extname(path).length);
siblingsPattern = { siblings, name };
}
siblingsPattern = TPromise.is(siblings) ?
siblings.then(siblingsToSiblingsPattern) :
siblingsToSiblingsPattern(siblings);
}

return siblingsPattern;
}

Expand Down Expand Up @@ -538,7 +560,16 @@ function parseExpressionPattern(pattern: string, value: any, options: IGlobOptio
if (value) {
const when = (<SiblingClause>value).when;
if (typeof when === 'string') {
const result: ParsedExpressionPattern = function (path: string, basename: string, siblingsPatternFn: () => SiblingsPattern) {
const siblingsPatternToMatchingPattern = (siblingsPattern: SiblingsPattern): string => {
let clausePattern = when.replace('$(basename)', siblingsPattern.name);
if (siblingsPattern.siblings.indexOf(clausePattern) !== -1) {
return pattern;
} else {
return null; // pattern does not match in the end because the when clause is not satisfied
}
};

const result: ParsedExpressionPattern = (path: string, basename: string, siblingsPatternFn: () => SiblingsPattern | TPromise<SiblingsPattern>) => {
if (!parsedPattern(path, basename)) {
return null;
}
Expand All @@ -548,12 +579,9 @@ function parseExpressionPattern(pattern: string, value: any, options: IGlobOptio
return null; // pattern is malformed or we don't have siblings
}

let clausePattern = when.replace('$(basename)', siblingsPattern.name);
if (siblingsPattern.siblings.indexOf(clausePattern) !== -1) {
return pattern;
} else {
return null; // pattern does not match in the end because the when clause is not satisfied
}
return TPromise.is(siblingsPattern) ?
siblingsPattern.then(siblingsPatternToMatchingPattern) :
siblingsPatternToMatchingPattern(siblingsPattern);
};
result.requiresSiblings = true;
return result;
Expand Down
47 changes: 39 additions & 8 deletions src/vs/workbench/services/search/node/ripgrepTextSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ import * as path from 'path';
import * as cp from 'child_process';
import { rgPath } from 'vscode-ripgrep';

import * as extfs from 'vs/base/node/extfs';
import * as encoding from 'vs/base/node/encoding';
import * as strings from 'vs/base/common/strings';
import * as glob from 'vs/base/common/glob';
import { ILineMatch, IProgress } from 'vs/platform/search/common/search';
import { TPromise } from 'vs/base/common/winjs.base';

import { ISerializedFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEngine } from './search';

export class RipgrepEngine implements ISearchEngine<ISerializedFileMatch> {
private isDone = false;
private rgProc: cp.ChildProcess;
private postProcessExclusions: glob.SiblingClause[];
private postProcessExclusions: glob.ParsedExpression;

private ripgrepParser: RipgrepParser;

Expand All @@ -47,13 +49,26 @@ export class RipgrepEngine implements ISearchEngine<ISerializedFileMatch> {

private searchFolder(rootFolder: string, onResult: (match: ISerializedFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void {
const rgArgs = getRgArgs(this.config);
this.postProcessExclusions = rgArgs.siblingClauses;
if (rgArgs.siblingClauses) {
this.postProcessExclusions = glob.parseToAsync(rgArgs.siblingClauses, { trimForExclusions: true });
}

// console.log(`rg ${rgArgs.args.join(' ')}, cwd: ${rootFolder}`);
this.rgProc = cp.spawn(rgPath, rgArgs.args, { cwd: rootFolder });

this.ripgrepParser = new RipgrepParser(this.config.maxResults, rootFolder);
this.ripgrepParser.on('result', onResult);
this.ripgrepParser.on('result', (match: ISerializedFileMatch) => {
if (this.postProcessExclusions) {
const relativePath = path.relative(rootFolder, match.path);
(<TPromise<string>>this.postProcessExclusions(relativePath, undefined, () => getSiblings(match.path))).then(globMatch => {
if (!globMatch) {
onResult(match);
}
});
} else {
onResult(match);
}
});
this.ripgrepParser.on('hitLimit', () => {
this.cancel();
done(null, {
Expand Down Expand Up @@ -281,9 +296,9 @@ export class LineMatch implements ILineMatch {
}
}

function globExprsToRgGlobs(patterns: glob.IExpression): { globArgs: string[], siblingClauses: glob.SiblingClause[] } {
function globExprsToRgGlobs(patterns: glob.IExpression): { globArgs: string[], siblingClauses: glob.IExpression } {
const globArgs: string[] = [];
const siblingClauses: glob.SiblingClause[] = [];
let siblingClauses: glob.IExpression = null;
Object.keys(patterns)
.forEach(key => {
const value = patterns[key];
Expand All @@ -295,14 +310,18 @@ function globExprsToRgGlobs(patterns: glob.IExpression): { globArgs: string[], s

globArgs.push(key);
} else if (value && value.when) {
siblingClauses.push(value);
if (!siblingClauses) {
siblingClauses = {};
}

siblingClauses[key] = value;
}
});

return { globArgs, siblingClauses };
}

function getRgArgs(config: IRawSearch): { args: string[], siblingClauses: glob.SiblingClause[] } {
function getRgArgs(config: IRawSearch): { args: string[], siblingClauses: glob.IExpression } {
const args = ['--heading', '--line-number', '--color', 'ansi', '--colors', 'path:none', '--colors', 'line:none', '--colors', 'match:fg:red', '--colors', 'match:style:nobold'];
args.push(config.contentPattern.isCaseSensitive ? '--case-sensitive' : '--ignore-case');

Expand All @@ -313,7 +332,7 @@ function getRgArgs(config: IRawSearch): { args: string[], siblingClauses: glob.S
});
}

let siblingClauses: glob.SiblingClause[] = [];
let siblingClauses: glob.IExpression;
if (config.excludePattern) {
const rgGlobs = globExprsToRgGlobs(config.excludePattern);
rgGlobs.globArgs
Expand Down Expand Up @@ -357,3 +376,15 @@ function getRgArgs(config: IRawSearch): { args: string[], siblingClauses: glob.S

return { args, siblingClauses };
}

function getSiblings(file: string): TPromise<string[]> {
return new TPromise((resolve, reject) => {
extfs.readdir(path.dirname(file), (error: Error, files: string[]) => {
if (error) {
reject(error);
}

resolve(files);
});
});
}

0 comments on commit 188ef21

Please sign in to comment.