diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 452db982996..ddbd36d08e2 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -15,6 +15,10 @@ export interface IExpression { [pattern: string]: boolean | SiblingClause | any; } +export function getEmptyExpression(): IExpression { + return Object.create(null); +} + export interface SiblingClause { when: string; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 93cb1b2ce7a..84b65bafdba 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -6,7 +6,7 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; -import { ISearchService, QueryType } from 'vs/platform/search/common/search'; +import { ISearchService, QueryType, ISearchQuery } from 'vs/platform/search/common/search'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -56,13 +56,16 @@ export class MainThreadWorkspace extends MainThreadWorkspaceShape { return undefined; } - const search = this._searchService.search({ + const query: ISearchQuery = { folderQueries: workspace.roots.map(root => ({ folder: root })), type: QueryType.File, maxResults, includePattern: { [include]: true }, excludePattern: { [exclude]: true }, - }).then(result => { + }; + this._searchService.extendQuery(query); + + const search = this._searchService.search(query).then(result => { return result.results.map(m => m.resource); }, err => { if (!isPromiseCanceledError(err)) { diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index 92d08a7823a..dd28ab6c62e 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -45,9 +45,6 @@ export class QueryBuilder { let { searchPaths, includePattern } = this.parseSearchPaths(options.includePattern); let excludePattern = patternListToIExpression(splitGlobPattern(options.excludePattern)); - // if we have searchPaths, then turn them into folderQueries, and get all the excludes for the workspaces, and add them to excludePattern. - // If we don't, then turn the folderResources into folderQueries, with their excludes on each one - // Build folderQueries from searchPaths, if given, otherwise folderResources let folderQueries = folderResources && folderResources.map(uri => this.getFolderQueryForRoot(uri, options)); if (searchPaths && searchPaths.length) { diff --git a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts index 78036bb448f..1917b3a4c1b 100644 --- a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts @@ -92,8 +92,6 @@ suite('QueryBuilder', () => { }); test('simple include', () => { - mockConfigService.setUserConfiguration('search', { useRipgrep: true }); - assertEqualQueries( queryBuilder.text( PATTERN_INFO, @@ -142,7 +140,6 @@ suite('QueryBuilder', () => { const ROOT_3_URI = getUri(ROOT_3); mockWorkspace.roots = [ROOT_1_URI, ROOT_2_URI, ROOT_3_URI]; - mockConfigService.setUserConfiguration('search', { useRipgrep: true }); mockConfigService.setUserConfiguration('search', { useRipgrep: true, exclude: { 'foo/**/*.js': true } diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index f77c6390bec..658fa7c507a 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -12,6 +12,7 @@ import fs = require('fs'); import path = require('path'); import { isEqualOrParent } from 'vs/base/common/paths'; import { Readable } from 'stream'; +import { TPromise } from 'vs/base/common/winjs.base'; import scorer = require('vs/base/common/scorer'); import objects = require('vs/base/common/objects'); @@ -103,10 +104,34 @@ export class FileWalker { } }); - this.folderExcludePatterns.set(folderQuery.folder, glob.parse(folderExcludeExpression, { trimForExclusions: true })); + this.folderExcludePatterns.set(folderQuery.folder, this.parseAbsoluteAndRelativeGlobs(folderExcludeExpression, folderQuery.folder)); }); } + private parseAbsoluteAndRelativeGlobs(expr: glob.IExpression, root: string): glob.ParsedExpression { + let absoluteGlobExpr: glob.IExpression; + let relativeGlobExpr: glob.IExpression; + Object.keys(expr).forEach(key => { + if (path.isAbsolute(key)) { + absoluteGlobExpr = absoluteGlobExpr || glob.getEmptyExpression(); + absoluteGlobExpr[key] = true; + } else { + relativeGlobExpr = relativeGlobExpr || glob.getEmptyExpression(); + relativeGlobExpr[key] = true; + } + }); + + const absoluteParsedExpr = absoluteGlobExpr && glob.parse(absoluteGlobExpr, { trimForExclusions: true }); + const relativeParsedExpr = relativeGlobExpr && glob.parse(relativeGlobExpr, { trimForExclusions: true }); + + // The absolute and relative expressions don't "have" to be kept separate, but this keeps us from having to path.join every single + // file searched, it's only used for a text search with a searchPath + return (_path: string, basename?: string, siblingsFn?: () => string[] | TPromise): string | TPromise => { + return (relativeParsedExpr && relativeParsedExpr(_path, basename, siblingsFn)) || + (absoluteParsedExpr && absoluteParsedExpr(path.join(root, _path), basename, siblingsFn)); + }; + } + public cancel(): void { this.isCanceled = true; } diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index 66982e8c96a..de5000fdcfe 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -88,7 +88,8 @@ function doRipgrepSearchTest(config: IRawSearch, expectedResultCount: number): T } function doSearchTest(config: IRawSearch, expectedResultCount: number, done) { - return doRipgrepSearchTest(config, expectedResultCount) + return doLegacySearchTest(config, expectedResultCount) + .then(() => doRipgrepSearchTest(config, expectedResultCount)) .then(done, done); } @@ -187,6 +188,26 @@ suite('Search-integration', function () { doSearchTest(config, 382, done); }); + test('Text: e (with absolute path excludes)', function (done: () => void) { + const config: any = { + folderQueries: ROOT_FOLDER_QUERY, + contentPattern: { pattern: 'e' }, + excludePattern: makeExpression(path.join(TEST_FIXTURES, '**/examples')) + }; + + doSearchTest(config, 394, done); + }); + + test('Text: e (with mixed absolute/relative path excludes)', function (done: () => void) { + const config: any = { + folderQueries: ROOT_FOLDER_QUERY, + contentPattern: { pattern: 'e' }, + excludePattern: makeExpression(path.join(TEST_FIXTURES, '**/examples'), '*.css') + }; + + doSearchTest(config, 310, done); + }); + test('Text: sibling exclude', function (done: () => void) { const config: any = { folderQueries: ROOT_FOLDER_QUERY,