提交 50bb7aad 编写于 作者: C Christof Marti

Prune find traversal with basename patterns

上级 3d1a7125
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import arrays = require('vs/base/common/arrays');
import strings = require('vs/base/common/strings'); import strings = require('vs/base/common/strings');
import paths = require('vs/base/common/paths'); import paths = require('vs/base/common/paths');
import {BoundedLinkedMap} from 'vs/base/common/map'; import {BoundedLinkedMap} from 'vs/base/common/map';
...@@ -219,11 +220,13 @@ interface ParsedStringPattern { ...@@ -219,11 +220,13 @@ interface ParsedStringPattern {
(path: string, basename: string): string /* the matching pattern */; (path: string, basename: string): string /* the matching pattern */;
basenames?: string[]; basenames?: string[];
patterns?: string[]; patterns?: string[];
allBasenames?: string[];
} }
type SiblingsPattern = { siblings: string[], name: string }; type SiblingsPattern = { siblings: string[], name: string };
interface ParsedExpressionPattern { interface ParsedExpressionPattern {
(path: string, basename: string, siblingsPatternFn: () => SiblingsPattern): string /* the matching pattern */; (path: string, basename: string, siblingsPatternFn: () => SiblingsPattern): string /* the matching pattern */;
requiresSiblings?: boolean; requiresSiblings?: boolean;
allBasenames?: string[];
} }
const CACHE = new BoundedLinkedMap<ParsedStringPattern>(10000); // bounded to 10000 elements const CACHE = new BoundedLinkedMap<ParsedStringPattern>(10000); // bounded to 10000 elements
...@@ -269,8 +272,10 @@ function parsePattern(pattern: string): ParsedStringPattern { ...@@ -269,8 +272,10 @@ function parsePattern(pattern: string): ParsedStringPattern {
} }
return path === base || strings.endsWith(path, slashBase) || strings.endsWith(path, backslashBase) ? pattern : null; return path === base || strings.endsWith(path, slashBase) || strings.endsWith(path, backslashBase) ? pattern : null;
}; };
parsedPattern.basenames = [base]; const basenames = [base];
parsedPattern.basenames = basenames;
parsedPattern.patterns = [pattern]; parsedPattern.patterns = [pattern];
parsedPattern.allBasenames = basenames;
} else if (T3.test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png} } else if (T3.test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png}
const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1).split(',') const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1).split(',')
.map(pattern => parsePattern(pattern)) .map(pattern => parsePattern(pattern))
...@@ -290,6 +295,10 @@ function parsePattern(pattern: string): ParsedStringPattern { ...@@ -290,6 +295,10 @@ function parsePattern(pattern: string): ParsedStringPattern {
} }
return null; return null;
}; };
const withBasenames = arrays.first(parsedPatterns, pattern => !!(<ParsedStringPattern>pattern).allBasenames);
if (withBasenames) {
parsedPattern.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
}
} }
// Otherwise convert to pattern // Otherwise convert to pattern
...@@ -354,15 +363,23 @@ export function parse(arg1: string | IExpression): any { ...@@ -354,15 +363,23 @@ export function parse(arg1: string | IExpression): any {
if (parsedPattern === NULL) { if (parsedPattern === NULL) {
return FALSE; return FALSE;
} }
return function (path: string, basename: string) { const resultPattern = function (path: string, basename: string) {
return !!parsedPattern(path, basename); return !!parsedPattern(path, basename);
}; };
if (parsedPattern.allBasenames) {
(<ParsedStringPattern><any>resultPattern).allBasenames = parsedPattern.allBasenames;
}
return resultPattern;
} }
// Glob with Expression // Glob with Expression
return parsedExpression(<IExpression>arg1); return parsedExpression(<IExpression>arg1);
} }
export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
return (<ParsedStringPattern>patternOrExpression).allBasenames || [];
}
function parsedExpression(expression: IExpression): ParsedExpression { function parsedExpression(expression: IExpression): ParsedExpression {
const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression) const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
.map(pattern => parseExpressionPattern(pattern, expression[pattern])) .map(pattern => parseExpressionPattern(pattern, expression[pattern]))
...@@ -378,7 +395,7 @@ function parsedExpression(expression: IExpression): ParsedExpression { ...@@ -378,7 +395,7 @@ function parsedExpression(expression: IExpression): ParsedExpression {
return <ParsedStringPattern>parsedPatterns[0]; return <ParsedStringPattern>parsedPatterns[0];
} }
return function (path: string, basename: string, siblingsFn?: () => string[]) { const resultExpression: ParsedStringPattern = function (path: string, basename: string, siblingsFn?: () => string[]) {
for (let i = 0, n = parsedPatterns.length; i < n; i++) { for (let i = 0, n = parsedPatterns.length; i < n; i++) {
// Pattern matches path // Pattern matches path
const result = (<ParsedStringPattern>parsedPatterns[i])(path, basename); const result = (<ParsedStringPattern>parsedPatterns[i])(path, basename);
...@@ -389,9 +406,16 @@ function parsedExpression(expression: IExpression): ParsedExpression { ...@@ -389,9 +406,16 @@ function parsedExpression(expression: IExpression): ParsedExpression {
return null; return null;
}; };
const withBasenames = arrays.first(parsedPatterns, pattern => !!(<ParsedStringPattern>pattern).allBasenames);
if (withBasenames) {
resultExpression.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
}
return resultExpression;
} }
return function (path: string, basename: string, siblingsFn?: () => string[]) { const resultExpression: ParsedStringPattern = function (path: string, basename: string, siblingsFn?: () => string[]) {
let siblingsPattern: SiblingsPattern; let siblingsPattern: SiblingsPattern;
let siblingsResolved = !siblingsFn; let siblingsResolved = !siblingsFn;
...@@ -421,6 +445,13 @@ function parsedExpression(expression: IExpression): ParsedExpression { ...@@ -421,6 +445,13 @@ function parsedExpression(expression: IExpression): ParsedExpression {
return null; return null;
}; };
const withBasenames = arrays.first(parsedPatterns, pattern => !!(<ParsedStringPattern>pattern).allBasenames);
if (withBasenames) {
resultExpression.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
}
return resultExpression;
} }
function parseExpressionPattern(pattern: string, value: any): (ParsedStringPattern | ParsedExpressionPattern) { function parseExpressionPattern(pattern: string, value: any): (ParsedStringPattern | ParsedExpressionPattern) {
...@@ -503,6 +534,7 @@ function aggregateBasenameMatches(parsedPatterns: (ParsedStringPattern | ParsedE ...@@ -503,6 +534,7 @@ function aggregateBasenameMatches(parsedPatterns: (ParsedStringPattern | ParsedE
}; };
aggregate.basenames = basenames; aggregate.basenames = basenames;
aggregate.patterns = patterns; aggregate.patterns = patterns;
aggregate.allBasenames = basenames;
const aggregatedPatterns = parsedPatterns.filter(parsedPattern => !(<ParsedStringPattern>parsedPattern).basenames); const aggregatedPatterns = parsedPatterns.filter(parsedPattern => !(<ParsedStringPattern>parsedPattern).basenames);
aggregatedPatterns.push(aggregate); aggregatedPatterns.push(aggregate);
......
...@@ -668,7 +668,7 @@ suite('Glob', () => { ...@@ -668,7 +668,7 @@ suite('Glob', () => {
assert.strictEqual(glob.parse('')('foo'), false); assert.strictEqual(glob.parse('')('foo'), false);
}); });
test('expression falsy path', function () { test('falsy path', function () {
assert.strictEqual(glob.parse('foo')(null), false); assert.strictEqual(glob.parse('foo')(null), false);
assert.strictEqual(glob.parse('foo')(''), false); assert.strictEqual(glob.parse('foo')(''), false);
assert.strictEqual(glob.parse('**/*.j?')(null), false); assert.strictEqual(glob.parse('**/*.j?')(null), false);
...@@ -683,7 +683,7 @@ suite('Glob', () => { ...@@ -683,7 +683,7 @@ suite('Glob', () => {
assert.strictEqual(glob.parse('{**/*.baz,**/*.foo}')(''), false); assert.strictEqual(glob.parse('{**/*.baz,**/*.foo}')(''), false);
}); });
test('expression basename', function () { test('expression/pattern basename', function () {
assert.strictEqual(glob.parse('**/foo')('bar/baz', 'baz'), false); assert.strictEqual(glob.parse('**/foo')('bar/baz', 'baz'), false);
assert.strictEqual(glob.parse('**/foo')('bar/foo', 'foo'), true); assert.strictEqual(glob.parse('**/foo')('bar/foo', 'foo'), true);
...@@ -696,4 +696,20 @@ suite('Glob', () => { ...@@ -696,4 +696,20 @@ suite('Glob', () => {
assert.strictEqual(glob.parse(expr)('bar/baz.js', 'baz.js', sibilings), null); assert.strictEqual(glob.parse(expr)('bar/baz.js', 'baz.js', sibilings), null);
assert.strictEqual(glob.parse(expr)('bar/foo.js', 'foo.js', sibilings), '**/*.js'); assert.strictEqual(glob.parse(expr)('bar/foo.js', 'foo.js', sibilings), '**/*.js');
}); });
test('expression/pattern basename terms', function () {
assert.deepStrictEqual(glob.getBasenameTerms(glob.parse('**/*.foo')), []);
assert.deepStrictEqual(glob.getBasenameTerms(glob.parse('**/foo')), ['foo']);
assert.deepStrictEqual(glob.getBasenameTerms(glob.parse('{**/baz,**/foo}')), ['baz', 'foo']);
assert.deepStrictEqual(glob.getBasenameTerms(glob.parse({
'**/foo': true,
'{**/bar,**/baz}': true,
'**/bulb': false
})), ['foo', 'bar', 'baz']);
assert.deepStrictEqual(glob.getBasenameTerms(glob.parse({
'**/foo': { when: '$(basename).zip' },
'**/bar': true
})), ['bar']);
});
}); });
\ No newline at end of file
...@@ -169,7 +169,7 @@ export class FileWalker { ...@@ -169,7 +169,7 @@ export class FileWalker {
} }
private macFindTraversal(rootFolder: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void { private macFindTraversal(rootFolder: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void {
const cmd = childProcess.spawn('find', ['-L', '.', '-type', 'f'], { cwd: rootFolder }); const cmd = this.spawnFindCmd(rootFolder, this.excludePattern);
this.readStdout(cmd, 'utf8', (err: Error, stdout?: string) => { this.readStdout(cmd, 'utf8', (err: Error, stdout?: string) => {
if (err) { if (err) {
done(err); done(err);
...@@ -224,7 +224,7 @@ export class FileWalker { ...@@ -224,7 +224,7 @@ export class FileWalker {
} }
private linuxFindTraversal(rootFolder: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void { private linuxFindTraversal(rootFolder: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void {
const cmd = childProcess.spawn('find', ['-L', '.', '-type', 'f'], { cwd: rootFolder }); const cmd = this.spawnFindCmd(rootFolder, this.excludePattern);
this.readStdout(cmd, 'utf8', (err: Error, stdout?: string) => { this.readStdout(cmd, 'utf8', (err: Error, stdout?: string) => {
if (err) { if (err) {
done(err); done(err);
...@@ -250,7 +250,36 @@ export class FileWalker { ...@@ -250,7 +250,36 @@ export class FileWalker {
}); });
} }
private readStdout(cmd: childProcess.ChildProcess, encoding: string, cb: (err: Error, stdout?: string) => void): void { /**
* Public for testing.
*/
public spawnFindCmd(rootFolder: string, excludePattern: glob.ParsedExpression) {
const basenames = glob.getBasenameTerms(excludePattern);
let args = ['-L', '.'];
if (basenames.length) {
args.push('-not', '(', '(');
for (let i = 0, n = basenames.length; i < n; i++) {
if (i) {
args.push('-o');
}
args.push('-name', FileWalker.escapeGlobSpecials(basenames[i]));
}
args.push(')', '-prune', ')');
}
args.push('-type', 'f');
return childProcess.spawn('find', args, { cwd: rootFolder });
}
private static GLOB_SPECIALS = /[*?\[\]\\]/g;
private static ESCAPE_CHAR = '\\$&';
private static escapeGlobSpecials(string) {
return string.replace(this.GLOB_SPECIALS, this.ESCAPE_CHAR);
}
/**
* Public for testing.
*/
public readStdout(cmd: childProcess.ChildProcess, encoding: string, cb: (err: Error, stdout?: string) => void): void {
let done = (err: Error, stdout?: string) => { let done = (err: Error, stdout?: string) => {
done = () => {}; done = () => {};
this.cmdForkResultTime = Date.now(); this.cmdForkResultTime = Date.now();
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
import path = require('path'); import path = require('path');
import assert = require('assert'); import assert = require('assert');
import * as glob from 'vs/base/common/glob';
import {join, normalize} from 'vs/base/common/paths'; import {join, normalize} from 'vs/base/common/paths';
import * as platform from 'vs/base/common/platform';
import {LineMatch} from 'vs/platform/search/common/search'; import {LineMatch} from 'vs/platform/search/common/search';
import {FileWalker, Engine as FileSearchEngine} from 'vs/workbench/services/search/node/fileSearch'; import {FileWalker, Engine as FileSearchEngine} from 'vs/workbench/services/search/node/fileSearch';
...@@ -119,7 +121,7 @@ suite('Search', () => { ...@@ -119,7 +121,7 @@ suite('Search', () => {
} }
}, () => { }, (error) => { }, () => { }, (error) => {
assert.ok(!error); assert.ok(!error);
assert.equal(count, 12); assert.equal(count, 13);
done(); done();
}); });
}); });
...@@ -178,7 +180,7 @@ suite('Search', () => { ...@@ -178,7 +180,7 @@ suite('Search', () => {
} }
}, () => { }, (error) => { }, () => { }, (error) => {
assert.ok(!error); assert.ok(!error);
assert.equal(count, 7); assert.equal(count, 8);
done(); done();
}); });
}); });
...@@ -197,7 +199,7 @@ suite('Search', () => { ...@@ -197,7 +199,7 @@ suite('Search', () => {
} }
}, () => { }, (error) => { }, () => { }, (error) => {
assert.ok(!error); assert.ok(!error);
assert.equal(count, 7); assert.equal(count, 8);
done(); done();
}); });
}); });
...@@ -216,7 +218,7 @@ suite('Search', () => { ...@@ -216,7 +218,7 @@ suite('Search', () => {
} }
}, () => { }, (error) => { }, () => { }, (error) => {
assert.ok(!error); assert.ok(!error);
assert.equal(count, 7); assert.equal(count, 8);
done(); done();
}); });
}); });
...@@ -235,7 +237,7 @@ suite('Search', () => { ...@@ -235,7 +237,7 @@ suite('Search', () => {
} }
}, () => { }, (error) => { }, () => { }, (error) => {
assert.ok(!error); assert.ok(!error);
assert.equal(count, 11); assert.equal(count, 12);
done(); done();
}); });
}); });
...@@ -421,6 +423,59 @@ suite('Search', () => { ...@@ -421,6 +423,59 @@ suite('Search', () => {
}); });
}); });
test('Find: exclude subfolder', function (done: () => void) {
if (platform.isWindows) {
return;
}
const walker = new FileWalker({ rootFolders: rootfolders() });
const file0 = './more/file.txt';
const file1 = './examples/subfolder/subfile.txt';
const cmd1 = walker.spawnFindCmd(rootfolders()[0], glob.parse({ '**/something': true }));
walker.readStdout(cmd1, 'utf8', (err1, stdout1) => {
assert.equal(err1, null);
assert.notStrictEqual(stdout1.split('\n').indexOf(file0), -1, stdout1);
assert.notStrictEqual(stdout1.split('\n').indexOf(file1), -1, stdout1);
const cmd2 = walker.spawnFindCmd(rootfolders()[0], glob.parse({ '**/subfolder': true }));
walker.readStdout(cmd2, 'utf8', (err2, stdout2) => {
assert.equal(err2, null);
assert.notStrictEqual(stdout1.split('\n').indexOf(file0), -1, stdout1);
assert.strictEqual(stdout2.split('\n').indexOf(file1), -1, stdout2);
done();
});
});
});
test('Find: exclude multiple folders', function (done: () => void) {
if (platform.isWindows) {
return;
}
const walker = new FileWalker({ rootFolders: rootfolders() });
const file0 = './index.html';
const file1 = './examples/small.js';
const file2 = './more/file.txt';
const cmd1 = walker.spawnFindCmd(rootfolders()[0], glob.parse({ '**/something': true }));
walker.readStdout(cmd1, 'utf8', (err1, stdout1) => {
assert.equal(err1, null);
assert.notStrictEqual(stdout1.split('\n').indexOf(file0), -1, stdout1);
assert.notStrictEqual(stdout1.split('\n').indexOf(file1), -1, stdout1);
assert.notStrictEqual(stdout1.split('\n').indexOf(file2), -1, stdout1);
const cmd2 = walker.spawnFindCmd(rootfolders()[0], glob.parse({ '{**/examples,**/more}': true }));
walker.readStdout(cmd2, 'utf8', (err2, stdout2) => {
assert.equal(err2, null);
assert.notStrictEqual(stdout1.split('\n').indexOf(file0), -1, stdout1);
assert.strictEqual(stdout2.split('\n').indexOf(file1), -1, stdout2);
assert.strictEqual(stdout2.split('\n').indexOf(file2), -1, stdout2);
done();
});
});
});
test('Text: GameOfLife', function (done: () => void) { test('Text: GameOfLife', function (done: () => void) {
let c = 0; let c = 0;
let config = { let config = {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册