From 96f7df4336059bf85cae5604cefc0fa388d79b7d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 22 Oct 2018 11:26:06 -0700 Subject: [PATCH] Refactor file search - use new interface for SearchService.fileSearch --- src/vs/workbench/api/node/extHostSearch.ts | 63 ++------- .../parts/search/common/queryBuilder.ts | 2 +- .../services/search/node/fileSearch.ts | 63 ++++----- .../node/legacy/rawLegacyTextSearchService.ts | 27 ++-- .../services/search/node/legacy/textSearch.ts | 49 ++++++- .../services/search/node/rawSearchService.ts | 66 ++-------- .../services/search/node/ripgrepFileSearch.ts | 22 ++-- .../workbench/services/search/node/search.ts | 5 +- .../services/search/node/searchIpc.ts | 7 +- .../services/search/node/searchService.ts | 45 +------ .../services/search/test/node/search.test.ts | 121 ++++++++++++------ .../search/test/node/searchService.test.ts | 48 ++++--- .../test/node/textSearch.integrationTest.ts | 48 +++---- 13 files changed, 264 insertions(+), 302 deletions(-) diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index c438c7fd386..cf20b24f285 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -12,11 +12,10 @@ import { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISea import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { FileIndexSearchManager } from 'vs/workbench/api/node/extHostSearch.fileIndex'; import { FileSearchManager } from 'vs/workbench/services/search/node/fileSearchManager'; -import { IFolderSearch, IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { RipgrepSearchProvider } from 'vs/workbench/services/search/node/ripgrepSearchProvider'; import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; -import { isSerializedFileMatch, isSerializedSearchComplete, isSerializedSearchSuccess } from 'vs/workbench/services/search/node/search'; +import { isSerializedFileMatch } from 'vs/workbench/services/search/node/search'; import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol'; @@ -136,56 +135,22 @@ export class ExtHostSearch implements ExtHostSearchShape { } private doInternalFileSearch(handle: number, session: number, rawQuery: IFileQuery, token: CancellationToken): Thenable { - return new Promise((resolve, reject) => { - const query: IRawSearch = { - folderQueries: [], - ignoreSymlinks: rawQuery.folderQueries.some(fq => fq.ignoreSymlinks), - filePattern: rawQuery.filePattern, - excludePattern: rawQuery.excludePattern, - includePattern: rawQuery.includePattern, - maxResults: rawQuery.maxResults, - exists: rawQuery.exists, - sortByScore: rawQuery.sortByScore, - cacheKey: rawQuery.cacheKey, - useRipgrep: rawQuery.useRipgrep, - disregardIgnoreFiles: rawQuery.folderQueries.some(fq => fq.disregardIgnoreFiles), - disregardGlobalIgnoreFiles: rawQuery.folderQueries.some(fq => fq.disregardGlobalIgnoreFiles), - }; - query.folderQueries = rawQuery.folderQueries.map(fq => ({ - disregardGlobalIgnoreFiles: fq.disregardGlobalIgnoreFiles, - disregardIgnoreFiles: fq.disregardIgnoreFiles, - excludePattern: fq.excludePattern, - fileEncoding: fq.fileEncoding, - folder: fq.folder.fsPath, - includePattern: fq.includePattern - })); + const onResult = (ev) => { + if (isSerializedFileMatch(ev)) { + ev = [ev]; + } - const event = this._internalFileSearchProvider.fileSearch(query); - event(ev => { - if (isSerializedSearchComplete(ev)) { - if (isSerializedSearchSuccess(ev)) { - resolve(ev); - return; - } else { - reject(ev); - return; - } - } else { - if (isSerializedFileMatch(ev)) { - ev = [ev]; - } + if (Array.isArray(ev)) { + this._proxy.$handleFileMatch(handle, session, ev.map(m => URI.file(m.path))); + return; + } - if (Array.isArray(ev)) { - this._proxy.$handleFileMatch(handle, session, ev.map(m => URI.file(m.path))); - return; - } + if (ev.message) { + this._logService.debug('ExtHostSearch', ev.message); + } + }; - if (ev.message) { - this._logService.debug('ExtHostSearch', ev.message); - } - } - }); - }); + return this._internalFileSearchProvider.doFileSearch(rawQuery, onResult, token); } $clearCache(cacheKey: string): Thenable { diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index cabdf13f113..c3e134aaae0 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -36,6 +36,7 @@ export interface ICommonQueryBuilderOptions { extraFileResources?: uri[]; maxResults?: number; + maxFileSize?: number; useRipgrep?: boolean; disregardIgnoreFiles?: boolean; disregardGlobalIgnoreFiles?: boolean; @@ -53,7 +54,6 @@ export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { export interface ITextQueryBuilderOptions extends ICommonQueryBuilderOptions { previewOptions?: ITextSearchPreviewOptions; fileEncoding?: string; - maxFileSize?: number; } export class QueryBuilder { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 79e240ab07f..b1ebe207c6e 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -18,11 +18,11 @@ import * as platform from 'vs/base/common/platform'; import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; import * as types from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import * as extfs from 'vs/base/node/extfs'; import * as flow from 'vs/base/node/flow'; -import { IProgress, ISearchEngineStats } from 'vs/platform/search/common/search'; -import { IFolderSearch, IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; +import { IFileQuery, IFolderQuery, IProgress, ISearchEngineStats } from 'vs/platform/search/common/search'; import { IRawFileMatch, ISearchEngine, ISearchEngineSuccess } from 'vs/workbench/services/search/node/search'; import { spawnRipgrepCmd } from './ripgrepFileSearch'; @@ -51,7 +51,7 @@ process.on('exit', () => { }); export class FileWalker { - private config: IRawSearch; + private config: IFileQuery; private useRipgrep: boolean; private filePattern: string; private normalizedFilePatternLowercase: string; @@ -75,14 +75,14 @@ export class FileWalker { private walkedPaths: { [path: string]: boolean; }; - constructor(config: IRawSearch) { + constructor(config: IFileQuery, maxFileSize?: number) { this.config = config; this.useRipgrep = config.useRipgrep !== false; this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || null; this.exists = config.exists; - this.maxFilesize = config.maxFilesize || null; + this.maxFilesize = maxFileSize || null; this.walkedPaths = Object.create(null); this.resultCount = 0; this.isLimitHit = false; @@ -102,17 +102,18 @@ export class FileWalker { const folderExcludeExpression: glob.IExpression = objects.assign({}, folderQuery.excludePattern || {}, this.config.excludePattern || {}); // Add excludes for other root folders + const fqPath = folderQuery.folder.fsPath; config.folderQueries - .map(rootFolderQuery => rootFolderQuery.folder) - .filter(rootFolder => rootFolder !== folderQuery.folder) + .map(rootFolderQuery => rootFolderQuery.folder.fsPath) + .filter(rootFolder => rootFolder !== fqPath) .forEach(otherRootFolder => { // Exclude nested root folders - if (isEqualOrParent(otherRootFolder, folderQuery.folder)) { - folderExcludeExpression[path.relative(folderQuery.folder, otherRootFolder)] = true; + if (isEqualOrParent(otherRootFolder, fqPath)) { + folderExcludeExpression[path.relative(fqPath, otherRootFolder)] = true; } }); - this.folderExcludePatterns.set(folderQuery.folder, new AbsoluteAndRelativeParsedExpression(folderExcludeExpression, folderQuery.folder)); + this.folderExcludePatterns.set(fqPath, new AbsoluteAndRelativeParsedExpression(folderExcludeExpression, fqPath)); }); } @@ -120,7 +121,7 @@ export class FileWalker { this.isCanceled = true; } - public walk(folderQueries: IFolderSearch[], extraFiles: string[], onResult: (result: IRawFileMatch) => void, onMessage: (message: IProgress) => void, done: (error: Error, isLimitHit: boolean) => void): void { + public walk(folderQueries: IFolderQuery[], extraFiles: URI[], onResult: (result: IRawFileMatch) => void, onMessage: (message: IProgress) => void, done: (error: Error, isLimitHit: boolean) => void): void { this.fileWalkSW = StopWatch.create(false); // Support that the file pattern is a full path to a file that exists @@ -131,13 +132,13 @@ export class FileWalker { // For each extra file if (extraFiles) { extraFiles.forEach(extraFilePath => { - const basename = path.basename(extraFilePath); - if (this.globalExcludePattern && this.globalExcludePattern(extraFilePath, basename)) { + const basename = path.basename(extraFilePath.fsPath); + if (this.globalExcludePattern && this.globalExcludePattern(extraFilePath.fsPath, basename)) { return; // excluded } // File: Check for match on file pattern and include pattern - this.matchFile(onResult, { relativePath: extraFilePath /* no workspace relative path */, basename }); + this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */, basename }); }); } @@ -165,7 +166,7 @@ export class FileWalker { } // For each root folder - flow.parallel(folderQueries, (folderQuery: IFolderSearch, rootFolderDone: (err: Error, result: void) => void) => { + flow.parallel(folderQueries, (folderQuery: IFolderQuery, rootFolderDone: (err: Error, result: void) => void) => { this.call(traverse, this, folderQuery, onResult, onMessage, (err?: Error) => { if (err) { const errorMessage = toErrorMessage(err); @@ -191,8 +192,8 @@ export class FileWalker { } } - private cmdTraversal(folderQuery: IFolderSearch, onResult: (result: IRawFileMatch) => void, onMessage: (message: IProgress) => void, cb: (err?: Error) => void): void { - const rootFolder = folderQuery.folder; + private cmdTraversal(folderQuery: IFolderQuery, onResult: (result: IRawFileMatch) => void, onMessage: (message: IProgress) => void, cb: (err?: Error) => void): void { + const rootFolder = folderQuery.folder.fsPath; const isMac = platform.isMacintosh; let cmd: childProcess.ChildProcess; const killCmd = () => cmd && cmd.kill(); @@ -210,7 +211,7 @@ export class FileWalker { const useRipgrep = this.useRipgrep; let noSiblingsClauses: boolean; if (useRipgrep) { - const ripgrep = spawnRipgrepCmd(this.config, folderQuery, this.config.includePattern, this.folderExcludePatterns.get(folderQuery.folder).expression); + const ripgrep = spawnRipgrepCmd(this.config, folderQuery, this.config.includePattern, this.folderExcludePatterns.get(folderQuery.folder.fsPath).expression); cmd = ripgrep.cmd; noSiblingsClauses = !Object.keys(ripgrep.siblingClauses).length; @@ -319,8 +320,8 @@ export class FileWalker { /** * Public for testing. */ - public spawnFindCmd(folderQuery: IFolderSearch) { - const excludePattern = this.folderExcludePatterns.get(folderQuery.folder); + public spawnFindCmd(folderQuery: IFolderQuery) { + const excludePattern = this.folderExcludePatterns.get(folderQuery.folder.fsPath); const basenames = excludePattern.getBasenameTerms(); const pathTerms = excludePattern.getPathTerms(); let args = ['-L', '.']; @@ -338,7 +339,7 @@ export class FileWalker { args.push(')', '-prune', ')'); } args.push('-type', 'f'); - return childProcess.spawn('find', args, { cwd: folderQuery.folder }); + return childProcess.spawn('find', args, { cwd: folderQuery.folder.fsPath }); } /** @@ -499,9 +500,9 @@ export class FileWalker { matchDirectory(rootEntries); } - private nodeJSTraversal(folderQuery: IFolderSearch, onResult: (result: IRawFileMatch) => void, onMessage: (message: IProgress) => void, done: (err?: Error) => void): void { + private nodeJSTraversal(folderQuery: IFolderQuery, onResult: (result: IRawFileMatch) => void, onMessage: (message: IProgress) => void, done: (err?: Error) => void): void { this.directoriesWalked++; - extfs.readdir(folderQuery.folder, (error: Error, files: string[]) => { + extfs.readdir(folderQuery.folder.fsPath, (error: Error, files: string[]) => { if (error || this.isCanceled || this.isLimitHit) { return done(); } @@ -525,7 +526,7 @@ export class FileWalker { }; } - private doWalk(folderQuery: IFolderSearch, relativeParentPath: string, files: string[], onResult: (result: IRawFileMatch) => void, done: (error: Error) => void): void { + private doWalk(folderQuery: IFolderQuery, relativeParentPath: string, files: string[], onResult: (result: IRawFileMatch) => void, done: (error: Error) => void): void { const rootFolder = folderQuery.folder; // Execute tasks on each file in parallel to optimize throughput @@ -542,12 +543,12 @@ export class FileWalker { // to ignore filtering by siblings because the user seems to know what she // is searching for and we want to include the result in that case anyway let currentRelativePath = relativeParentPath ? [relativeParentPath, file].join(path.sep) : file; - if (this.folderExcludePatterns.get(folderQuery.folder).test(currentRelativePath, file, this.config.filePattern !== file ? hasSibling : undefined)) { + if (this.folderExcludePatterns.get(folderQuery.folder.fsPath).test(currentRelativePath, file, this.config.filePattern !== file ? hasSibling : undefined)) { return clb(null, undefined); } // Use lstat to detect links - let currentAbsolutePath = [rootFolder, currentRelativePath].join(path.sep); + let currentAbsolutePath = [rootFolder.fsPath, currentRelativePath].join(path.sep); fs.lstat(currentAbsolutePath, (error, lstat) => { if (error || this.isCanceled || this.isLimitHit) { return clb(null, undefined); @@ -599,7 +600,7 @@ export class FileWalker { return clb(null, undefined); // ignore file if max file size is hit } - this.matchFile(onResult, { base: rootFolder, relativePath: currentRelativePath, basename: file, size: stat.size }); + this.matchFile(onResult, { base: rootFolder.fsPath, relativePath: currentRelativePath, basename: file, size: stat.size }); } // Unwind @@ -668,13 +669,13 @@ export class FileWalker { } export class Engine implements ISearchEngine { - private folderQueries: IFolderSearch[]; - private extraFiles: string[]; + private folderQueries: IFolderQuery[]; + private extraFiles: URI[]; private walker: FileWalker; - constructor(config: IRawSearch) { + constructor(config: IFileQuery) { this.folderQueries = config.folderQueries; - this.extraFiles = config.extraFiles; + this.extraFiles = config.extraFileResources; this.walker = new FileWalker(config); } diff --git a/src/vs/workbench/services/search/node/legacy/rawLegacyTextSearchService.ts b/src/vs/workbench/services/search/node/legacy/rawLegacyTextSearchService.ts index aa895c1d4fe..c1fd4d774f9 100644 --- a/src/vs/workbench/services/search/node/legacy/rawLegacyTextSearchService.ts +++ b/src/vs/workbench/services/search/node/legacy/rawLegacyTextSearchService.ts @@ -7,12 +7,12 @@ import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; import { CancellationToken } from 'vs/base/common/cancellation'; import { MAX_FILE_SIZE } from 'vs/platform/files/node/files'; +import { ITextQuery, QueryType } from 'vs/platform/search/common/search'; import { FileWalker } from 'vs/workbench/services/search/node/fileSearch'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; -import { BatchedCollector } from 'vs/workbench/services/search/node/textSearchManager'; -import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/legacy/textSearchWorkerProvider'; -import { ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from '../search'; import { Engine } from 'vs/workbench/services/search/node/legacy/textSearch'; +import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/legacy/textSearchWorkerProvider'; +import { BatchedCollector } from 'vs/workbench/services/search/node/textSearchManager'; +import { ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from '../search'; gracefulFs.gracefulify(fs); @@ -23,7 +23,7 @@ export class LegacyTextSearchService { private textSearchWorkerProvider: TextSearchWorkerProvider; - textSearch(config: IRawSearch, progressCallback: IProgressCallback, token: CancellationToken): Promise { + textSearch(config: ITextQuery, progressCallback: IProgressCallback, token?: CancellationToken): Promise { if (!this.textSearchWorkerProvider) { this.textSearchWorkerProvider = new TextSearchWorkerProvider(); } @@ -31,21 +31,22 @@ export class LegacyTextSearchService { let engine = new Engine( config, new FileWalker({ + type: QueryType.File, folderQueries: config.folderQueries, - extraFiles: config.extraFiles, + extraFileResources: config.extraFileResources, includePattern: config.includePattern, excludePattern: config.excludePattern, - filePattern: config.filePattern, - useRipgrep: false, - maxFilesize: MAX_FILE_SIZE - }), + useRipgrep: false + }, MAX_FILE_SIZE), this.textSearchWorkerProvider); return this.doTextSearch(engine, progressCallback, LegacyTextSearchService.BATCH_SIZE, token); } - private doTextSearch(engine: Engine, progressCallback: IProgressCallback, batchSize: number, token: CancellationToken): Promise { - token.onCancellationRequested(() => engine.cancel()); + private doTextSearch(engine: Engine, progressCallback: IProgressCallback, batchSize: number, token?: CancellationToken): Promise { + if (token) { + token.onCancellationRequested(() => engine.cancel()); + } return new Promise((c, e) => { // Use BatchedCollector to get new results to the frontend every 2s at least, until 50 results have been returned @@ -70,4 +71,4 @@ export class LegacyTextSearchService { }); }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/search/node/legacy/textSearch.ts b/src/vs/workbench/services/search/node/legacy/textSearch.ts index 63756c5589b..f4fc9ba2d7b 100644 --- a/src/vs/workbench/services/search/node/legacy/textSearch.ts +++ b/src/vs/workbench/services/search/node/legacy/textSearch.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IProgress } from 'vs/platform/search/common/search'; +import { IProgress, ITextQuery } from 'vs/platform/search/common/search'; import { FileWalker } from 'vs/workbench/services/search/node/fileSearch'; import { ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch } from '../search'; import { ITextSearchWorkerProvider } from './textSearchWorkerProvider'; @@ -17,6 +17,7 @@ export class Engine implements ISearchEngine { private static readonly PROGRESS_FLUSH_CHUNK_SIZE = 50; // optimization: number of files to process before emitting progress event private config: IRawSearch; + private config2: ITextQuery; private walker: FileWalker; private walkerError: Error; @@ -34,8 +35,9 @@ export class Engine implements ISearchEngine { private nextWorker = 0; - constructor(config: IRawSearch, walker: FileWalker, workerProvider: ITextSearchWorkerProvider) { - this.config = config; + constructor(config: ITextQuery, walker: FileWalker, workerProvider: ITextSearchWorkerProvider) { + this.config = makeRawSearch(config); + this.config2 = config; this.walker = walker; this.workerProvider = workerProvider; } @@ -123,7 +125,7 @@ export class Engine implements ISearchEngine { let nextBatch: string[] = []; let nextBatchBytes = 0; const batchFlushBytes = 2 ** 20; // 1MB - this.walker.walk(this.config.folderQueries, this.config.extraFiles, result => { + this.walker.walk(this.config2.folderQueries, this.config2.extraFileResources, result => { let bytes = result.size || 1; this.totalBytes += bytes; @@ -163,3 +165,42 @@ export class Engine implements ISearchEngine { }); } } + +/** + * Exported for tests + */ +export function makeRawSearch(query: ITextQuery): IRawSearch { + let rawSearch: IRawSearch = { + folderQueries: [], + extraFiles: [], + excludePattern: query.excludePattern, + includePattern: query.includePattern, + maxResults: query.maxResults, + useRipgrep: query.useRipgrep, + disregardIgnoreFiles: query.folderQueries.some(fq => fq.disregardIgnoreFiles), + disregardGlobalIgnoreFiles: query.folderQueries.some(fq => fq.disregardGlobalIgnoreFiles), + ignoreSymlinks: query.folderQueries.some(fq => fq.ignoreSymlinks), + previewOptions: query.previewOptions + }; + + for (const q of query.folderQueries) { + rawSearch.folderQueries.push({ + excludePattern: q.excludePattern, + includePattern: q.includePattern, + fileEncoding: q.fileEncoding, + disregardIgnoreFiles: q.disregardIgnoreFiles, + disregardGlobalIgnoreFiles: q.disregardGlobalIgnoreFiles, + folder: q.folder.fsPath + }); + } + + if (query.extraFileResources) { + for (const r of query.extraFileResources) { + rawSearch.extraFiles.push(r.fsPath); + } + } + + rawSearch.contentPattern = query.contentPattern; + + return rawSearch; +} diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 3ac1eec40a7..0a6f6efa94e 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -14,17 +14,15 @@ import { Emitter, Event } from 'vs/base/common/event'; import * as objects from 'vs/base/common/objects'; import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { MAX_FILE_SIZE } from 'vs/platform/files/node/files'; -import { ICachedSearchStats, IFileSearchStats, IProgress, IRawTextQuery, ITextQuery, IRawQuery, IFileQuery, IFolderQuery } from 'vs/platform/search/common/search'; +import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery } from 'vs/platform/search/common/search'; import { Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; import { LegacyTextSearchService } from 'vs/workbench/services/search/node/legacy/rawLegacyTextSearchService'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; import { IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from './search'; -import { Schemas } from 'vs/base/common/network'; -import { URI, UriComponents } from 'vs/base/common/uri'; gracefulFs.gracefulify(fs); @@ -38,13 +36,14 @@ export class SearchService implements IRawSearchService { private legacyTextSearchService = new LegacyTextSearchService(); private caches: { [cacheKey: string]: Cache; } = Object.create(null); - public fileSearch(config: IRawSearch, batchSize = SearchService.BATCH_SIZE): Event { + public fileSearch(config: IRawFileQuery): Event { let promise: CancelablePromise; + const query = reviveQuery(config); const emitter = new Emitter({ onFirstListenerDidAdd: () => { promise = createCancelablePromise(token => { - return this.doFileSearch(FileSearchEngine, config, p => emitter.fire(p), token, batchSize); + return this.doFileSearchWithEngine(FileSearchEngine, query, p => emitter.fire(p), token); }); promise.then( @@ -68,7 +67,7 @@ export class SearchService implements IRawSearchService { promise = createCancelablePromise(token => { return (rawQuery.useRipgrep ? this.ripgrepTextSearch(query, p => emitter.fire(p), token) : - this.legacyTextSearchService.textSearch(rawSearchQuery(query), p => emitter.fire(p), token)); + this.legacyTextSearchService.textSearch(query, p => emitter.fire(p), token)); }); promise.then( @@ -90,7 +89,11 @@ export class SearchService implements IRawSearchService { return engine.search(token, progressCallback, progressCallback); } - doFileSearch(EngineClass: { new(config: IRawSearch): ISearchEngine; }, config: IRawSearch, progressCallback: IProgressCallback, token?: CancellationToken, batchSize?: number): TPromise { + doFileSearch(config: IFileQuery, progressCallback: IProgressCallback, token?: CancellationToken): TPromise { + return this.doFileSearchWithEngine(FileSearchEngine, config, progressCallback, token); + } + + doFileSearchWithEngine(EngineClass: { new(config: IFileQuery): ISearchEngine; }, config: IFileQuery, progressCallback: IProgressCallback, token?: CancellationToken, batchSize = SearchService.BATCH_SIZE): TPromise { let resultCount = 0; const fileProgressCallback: IFileProgressCallback = progress => { if (Array.isArray(progress)) { @@ -142,7 +145,7 @@ export class SearchService implements IRawSearchService { return { path: match.base ? join(match.base, match.relativePath) : match.relativePath }; } - private doSortedSearch(engine: ISearchEngine, config: IRawSearch, progressCallback: IProgressCallback, fileProgressCallback: IFileProgressCallback, token?: CancellationToken): TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]> { + private doSortedSearch(engine: ISearchEngine, config: IFileQuery, progressCallback: IProgressCallback, fileProgressCallback: IFileProgressCallback, token?: CancellationToken): TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]> { const emitter = new Emitter(); let allResultsPromise = createCancelablePromise(token => { @@ -216,7 +219,7 @@ export class SearchService implements IRawSearchService { return this.caches[cacheKey] = new Cache(); } - private trySortedSearchFromCache(config: IRawSearch, progressCallback: IFileProgressCallback, token?: CancellationToken): TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]> { + private trySortedSearchFromCache(config: IFileQuery, progressCallback: IFileProgressCallback, token?: CancellationToken): TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]> { const cache = config.cacheKey && this.caches[config.cacheKey]; if (!cache) { return undefined; @@ -251,7 +254,7 @@ export class SearchService implements IRawSearchService { return undefined; } - private sortResults(config: IRawSearch, results: IRawFileMatch[], scorerCache: ScorerCache, token?: CancellationToken): TPromise { + private sortResults(config: IFileQuery, results: IRawFileMatch[], scorerCache: ScorerCache, token?: CancellationToken): TPromise { // we use the same compare function that is used later when showing the results using fuzzy scoring // this is very important because we are also limiting the number of results by config.maxResults // and as such we want the top items to be included in this result set if the number of items @@ -443,44 +446,3 @@ function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolder folder: URI.revive(rawFolderQuery.folder) }; } - -/** - * Exported for tests - */ -export function rawSearchQuery(query: ITextQuery): IRawSearch { - let rawSearch: IRawSearch = { - folderQueries: [], - extraFiles: [], - excludePattern: query.excludePattern, - includePattern: query.includePattern, - maxResults: query.maxResults, - useRipgrep: query.useRipgrep, - disregardIgnoreFiles: query.folderQueries.some(fq => fq.disregardIgnoreFiles), - disregardGlobalIgnoreFiles: query.folderQueries.some(fq => fq.disregardGlobalIgnoreFiles), - ignoreSymlinks: query.folderQueries.some(fq => fq.ignoreSymlinks), - previewOptions: query.previewOptions - }; - - for (const q of query.folderQueries) { - rawSearch.folderQueries.push({ - excludePattern: q.excludePattern, - includePattern: q.includePattern, - fileEncoding: q.fileEncoding, - disregardIgnoreFiles: q.disregardIgnoreFiles, - disregardGlobalIgnoreFiles: q.disregardGlobalIgnoreFiles, - folder: q.folder.fsPath - }); - } - - if (query.extraFileResources) { - for (const r of query.extraFileResources) { - if (r.scheme === Schemas.file) { - rawSearch.extraFiles.push(r.fsPath); - } - } - } - - rawSearch.contentPattern = query.contentPattern; - - return rawSearch; -} diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index 4b91216a1ee..950b35ccb8a 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -11,16 +11,16 @@ import * as objects from 'vs/base/common/objects'; import * as paths from 'vs/base/common/paths'; import { isMacintosh as isMac } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; -import { rgPath } from 'vscode-ripgrep'; +import { IFileQuery, IFolderQuery } from 'vs/platform/search/common/search'; import { anchorGlob } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; -import { IRawSearch, IFolderSearch } from 'vs/workbench/services/search/node/legacy/search'; +import { rgPath } from 'vscode-ripgrep'; // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); -export function spawnRipgrepCmd(config: IRawSearch, folderQuery: IFolderSearch, includePattern: glob.IExpression, excludePattern: glob.IExpression) { +export function spawnRipgrepCmd(config: IFileQuery, folderQuery: IFolderQuery, includePattern: glob.IExpression, excludePattern: glob.IExpression) { const rgArgs = getRgArgs(config, folderQuery, includePattern, excludePattern); - const cwd = folderQuery.folder; + const cwd = folderQuery.folder.fsPath; return { cmd: cp.spawn(rgDiskPath, rgArgs.args, { cwd }), siblingClauses: rgArgs.siblingClauses, @@ -29,7 +29,7 @@ export function spawnRipgrepCmd(config: IRawSearch, folderQuery: IFolderSearch, }; } -function getRgArgs(config: IRawSearch, folderQuery: IFolderSearch, includePattern: glob.IExpression, excludePattern: glob.IExpression) { +function getRgArgs(config: IFileQuery, folderQuery: IFolderQuery, includePattern: glob.IExpression, excludePattern: glob.IExpression) { const args = ['--files', '--hidden', '--case-sensitive']; // includePattern can't have siblingClauses @@ -67,7 +67,7 @@ function getRgArgs(config: IRawSearch, folderQuery: IFolderSearch, includePatter } // Follow symlinks - if (!config.ignoreSymlinks) { + if (!folderQuery.ignoreSymlinks) { args.push('--follow'); } @@ -76,7 +76,7 @@ function getRgArgs(config: IRawSearch, folderQuery: IFolderSearch, includePatter } args.push('--no-config'); - if (config.disregardGlobalIgnoreFiles) { + if (folderQuery.disregardGlobalIgnoreFiles) { args.push('--no-ignore-global'); } @@ -88,12 +88,12 @@ export interface IRgGlobResult { siblingClauses: glob.IExpression; } -export function foldersToRgExcludeGlobs(folderQueries: IFolderSearch[], globalExclude: glob.IExpression, excludesToSkip?: Set, absoluteGlobs = true): IRgGlobResult { +export function foldersToRgExcludeGlobs(folderQueries: IFolderQuery[], globalExclude: glob.IExpression, excludesToSkip?: Set, absoluteGlobs = true): IRgGlobResult { const globArgs: string[] = []; let siblingClauses: glob.IExpression = {}; folderQueries.forEach(folderQuery => { const totalExcludePattern = objects.assign({}, folderQuery.excludePattern || {}, globalExclude || {}); - const result = globExprsToRgGlobs(totalExcludePattern, absoluteGlobs && folderQuery.folder, excludesToSkip); + const result = globExprsToRgGlobs(totalExcludePattern, absoluteGlobs && folderQuery.folder.fsPath, excludesToSkip); globArgs.push(...result.globArgs); if (result.siblingClauses) { siblingClauses = objects.assign(siblingClauses, result.siblingClauses); @@ -103,11 +103,11 @@ export function foldersToRgExcludeGlobs(folderQueries: IFolderSearch[], globalEx return { globArgs, siblingClauses }; } -export function foldersToIncludeGlobs(folderQueries: IFolderSearch[], globalInclude: glob.IExpression, absoluteGlobs = true): string[] { +export function foldersToIncludeGlobs(folderQueries: IFolderQuery[], globalInclude: glob.IExpression, absoluteGlobs = true): string[] { const globArgs: string[] = []; folderQueries.forEach(folderQuery => { const totalIncludePattern = objects.assign({}, globalInclude || {}, folderQuery.includePattern || {}); - const result = globExprsToRgGlobs(totalIncludePattern, absoluteGlobs && folderQuery.folder); + const result = globExprsToRgGlobs(totalIncludePattern, absoluteGlobs && folderQuery.folder.fsPath); globArgs.push(...result.globArgs); }); diff --git a/src/vs/workbench/services/search/node/search.ts b/src/vs/workbench/services/search/node/search.ts index 8b91c35fcb3..4fc697c2273 100644 --- a/src/vs/workbench/services/search/node/search.ts +++ b/src/vs/workbench/services/search/node/search.ts @@ -6,9 +6,8 @@ import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IFileSearchStats, IFolderQuery, IProgress, ISearchEngineStats, ISearchQuery, ITextSearchResult, ITextSearchStats, IRawTextQuery } from 'vs/platform/search/common/search'; +import { IFileSearchStats, IFolderQuery, IProgress, IRawTextQuery, ISearchEngineStats, ISearchQuery, ITextSearchResult, ITextSearchStats, IRawFileQuery } from 'vs/platform/search/common/search'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; export interface ITelemetryEvent { eventName: string; @@ -16,7 +15,7 @@ export interface ITelemetryEvent { } export interface IRawSearchService { - fileSearch(search: IRawSearch): Event; + fileSearch(search: IRawFileQuery): Event; textSearch(search: IRawTextQuery): Event; clearCache(cacheKey: string): TPromise; } diff --git a/src/vs/workbench/services/search/node/searchIpc.ts b/src/vs/workbench/services/search/node/searchIpc.ts index 28770ed6811..4703fa6648a 100644 --- a/src/vs/workbench/services/search/node/searchIpc.ts +++ b/src/vs/workbench/services/search/node/searchIpc.ts @@ -6,12 +6,11 @@ import { Event } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; +import { IRawFileQuery, IRawTextQuery } from 'vs/platform/search/common/search'; import { IRawSearchService, ISerializedSearchComplete, ISerializedSearchProgressItem } from './search'; -import { IRawTextQuery } from 'vs/platform/search/common/search'; export interface ISearchChannel extends IChannel { - listen(event: 'fileSearch', search: IRawSearch): Event; + listen(event: 'fileSearch', search: IRawFileQuery): Event; listen(event: 'textSearch', search: IRawTextQuery): Event; call(command: 'clearCache', cacheKey: string): TPromise; call(command: string, arg: any): TPromise; @@ -41,7 +40,7 @@ export class SearchChannelClient implements IRawSearchService { constructor(private channel: ISearchChannel) { } - fileSearch(search: IRawSearch): Event { + fileSearch(search: IRawFileQuery): Event { return this.channel.listen('fileSearch', search); } diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 472cf549fdb..ffe674f6c91 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -28,7 +28,6 @@ import { FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from './search'; import { ISearchChannel, SearchChannelClient } from './searchIpc'; @@ -213,7 +212,7 @@ export class SearchService extends Disposable implements ISearchService { } }); - const diskSearchExtraFileResources = query.extraFileResources && query.extraFileResources.filter(res => res.scheme === 'file'); + const diskSearchExtraFileResources = query.extraFileResources && query.extraFileResources.filter(res => res.scheme === Schemas.file); if (diskSearchQueries.length || diskSearchExtraFileResources) { const diskSearchQuery: ISearchQuery = { @@ -507,52 +506,14 @@ export class DiskSearch implements ISearchResultProvider { throw canceled(); } - const existingFolders = folderQueries.filter((q, index) => exists[index]); - const rawSearch = this.rawSearchQuery(query, existingFolders); - + query.folderQueries = folderQueries.filter((q, index) => exists[index]); let event: Event; - event = this.raw.fileSearch(rawSearch); + event = this.raw.fileSearch(query); return DiskSearch.collectResultsFromEvent(event, null, token); }); } - private rawSearchQuery(query: IFileQuery, existingFolders: IFolderQuery[]) { - let rawSearch: IRawSearch = { - folderQueries: [], - extraFiles: [], - filePattern: query.filePattern, - excludePattern: query.excludePattern, - includePattern: query.includePattern, - maxResults: query.maxResults, - exists: query.exists, - sortByScore: query.sortByScore, - cacheKey: query.cacheKey, - useRipgrep: query.useRipgrep - }; - - for (const q of existingFolders) { - rawSearch.folderQueries.push({ - excludePattern: q.excludePattern, - includePattern: q.includePattern, - fileEncoding: q.fileEncoding, - disregardIgnoreFiles: q.disregardIgnoreFiles, - disregardGlobalIgnoreFiles: q.disregardGlobalIgnoreFiles, - folder: q.folder.fsPath - }); - } - - if (query.extraFileResources) { - for (const r of query.extraFileResources) { - if (r.scheme === Schemas.file) { - rawSearch.extraFiles.push(r.fsPath); - } - } - } - - return rawSearch; - } - public static collectResultsFromEvent(event: Event, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise { let result: IFileMatch[] = []; diff --git a/src/vs/workbench/services/search/test/node/search.test.ts b/src/vs/workbench/services/search/test/node/search.test.ts index 1f89b9c81e2..1c32b06c8a6 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -3,30 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; import * as assert from 'assert'; - +import * as path from 'path'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; import { join, normalize } from 'vs/base/common/paths'; import * as platform from 'vs/base/common/platform'; - -import { FileWalker, Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { IFolderSearch } from 'vs/workbench/services/search/node/legacy/search'; +import { joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { IFolderQuery, QueryType } from 'vs/platform/search/common/search'; +import { Engine as FileSearchEngine, FileWalker } from 'vs/workbench/services/search/node/fileSearch'; import { IRawFileMatch } from 'vs/workbench/services/search/node/search'; const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); -const EXAMPLES_FIXTURES = path.join(TEST_FIXTURES, 'examples'); -const MORE_FIXTURES = path.join(TEST_FIXTURES, 'more'); -const TEST_ROOT_FOLDER: IFolderSearch = { folder: TEST_FIXTURES }; -const ROOT_FOLDER_QUERY: IFolderSearch[] = [ +const EXAMPLES_FIXTURES = URI.file(path.join(TEST_FIXTURES, 'examples')); +const MORE_FIXTURES = URI.file(path.join(TEST_FIXTURES, 'more')); +const TEST_ROOT_FOLDER: IFolderQuery = { folder: URI.file(TEST_FIXTURES) }; +const ROOT_FOLDER_QUERY: IFolderQuery[] = [ TEST_ROOT_FOLDER ]; -const ROOT_FOLDER_QUERY_36438: IFolderSearch[] = [ - { folder: path.normalize(getPathFromAmdModule(require, './fixtures2/36438')) } +const ROOT_FOLDER_QUERY_36438: IFolderQuery[] = [ + { folder: URI.file(path.normalize(getPathFromAmdModule(require, './fixtures2/36438'))) } ]; -const MULTIROOT_QUERIES: IFolderSearch[] = [ +const MULTIROOT_QUERIES: IFolderQuery[] = [ { folder: EXAMPLES_FIXTURES }, { folder: MORE_FIXTURES } ]; @@ -38,6 +38,7 @@ suite('FileSearchEngine', () => { test('Files: *.js', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '*.js' }); @@ -57,6 +58,7 @@ suite('FileSearchEngine', () => { test('Files: maxResults', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, maxResults: 1 }); @@ -76,6 +78,7 @@ suite('FileSearchEngine', () => { test('Files: maxResults without Ripgrep', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, maxResults: 1, useRipgrep: false @@ -96,6 +99,7 @@ suite('FileSearchEngine', () => { test('Files: exists', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, includePattern: { '**/file.txt': true }, exists: true @@ -117,6 +121,7 @@ suite('FileSearchEngine', () => { test('Files: not exists', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, includePattern: { '**/nofile.txt': true }, exists: true @@ -138,6 +143,7 @@ suite('FileSearchEngine', () => { test('Files: exists without Ripgrep', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, includePattern: { '**/file.txt': true }, exists: true, @@ -160,6 +166,7 @@ suite('FileSearchEngine', () => { test('Files: not exists without Ripgrep', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, includePattern: { '**/nofile.txt': true }, exists: true, @@ -182,6 +189,7 @@ suite('FileSearchEngine', () => { test('Files: examples/com*', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: normalize(join('examples', 'com*'), true) }); @@ -201,6 +209,7 @@ suite('FileSearchEngine', () => { test('Files: examples (fuzzy)', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: 'xl' }); @@ -220,6 +229,7 @@ suite('FileSearchEngine', () => { test('Files: multiroot', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: MULTIROOT_QUERIES, filePattern: 'file' }); @@ -239,6 +249,7 @@ suite('FileSearchEngine', () => { test('Files: multiroot with includePattern and maxResults', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: MULTIROOT_QUERIES, maxResults: 1, includePattern: { @@ -263,6 +274,7 @@ suite('FileSearchEngine', () => { test('Files: multiroot with includePattern and exists', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: MULTIROOT_QUERIES, exists: true, includePattern: { @@ -288,6 +300,7 @@ suite('FileSearchEngine', () => { test('Files: NPE (CamelCase)', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: 'NullPE' }); @@ -307,6 +320,7 @@ suite('FileSearchEngine', () => { test('Files: *.*', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '*.*' }); @@ -326,6 +340,7 @@ suite('FileSearchEngine', () => { test('Files: *.as', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '*.as' }); @@ -345,6 +360,7 @@ suite('FileSearchEngine', () => { test('Files: *.* without derived', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: 'site.*', excludePattern: { '**/*.css': { 'when': '$(basename).less' } } @@ -368,6 +384,7 @@ suite('FileSearchEngine', () => { test('Files: *.* exclude folder without wildcard', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '*.*', excludePattern: { 'examples': true } @@ -388,6 +405,7 @@ suite('FileSearchEngine', () => { test('Files: exclude folder without wildcard #36438', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY_36438, excludePattern: { 'modules': true } }); @@ -407,6 +425,7 @@ suite('FileSearchEngine', () => { test('Files: include folder without wildcard #36438', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY_36438, includePattern: { 'modules/**': true } }); @@ -426,6 +445,7 @@ suite('FileSearchEngine', () => { test('Files: *.* exclude folder with leading wildcard', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '*.*', excludePattern: { '**/examples': true } @@ -446,6 +466,7 @@ suite('FileSearchEngine', () => { test('Files: *.* exclude folder with trailing wildcard', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '*.*', excludePattern: { 'examples/**': true } @@ -466,6 +487,7 @@ suite('FileSearchEngine', () => { test('Files: *.* exclude with unicode', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '*.*', excludePattern: { '**/üm laut汉语': true } @@ -486,6 +508,7 @@ suite('FileSearchEngine', () => { test('Files: *.* include with unicode', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '*.*', includePattern: { '**/üm laut汉语/*': true } @@ -505,7 +528,7 @@ suite('FileSearchEngine', () => { test('Files: multiroot with exclude', function (done: () => void) { this.timeout(testTimeout); - const folderQueries: IFolderSearch[] = [ + const folderQueries: IFolderQuery[] = [ { folder: EXAMPLES_FIXTURES, excludePattern: { @@ -521,6 +544,7 @@ suite('FileSearchEngine', () => { ]; const engine = new FileSearchEngine({ + type: QueryType.File, folderQueries, filePattern: '*' }); @@ -540,6 +564,7 @@ suite('FileSearchEngine', () => { test('Files: Unicode and Spaces', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: '汉语' }); @@ -562,6 +587,7 @@ suite('FileSearchEngine', () => { test('Files: no results', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: 'nofilematch' }); @@ -581,6 +607,7 @@ suite('FileSearchEngine', () => { test('Files: relative path matched once', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, filePattern: path.normalize(path.join('examples', 'company.js')) }); @@ -603,6 +630,7 @@ suite('FileSearchEngine', () => { test('Files: Include pattern, single files', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, includePattern: { 'site.css': true, @@ -627,11 +655,12 @@ suite('FileSearchEngine', () => { test('Files: extraFiles only', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: [], - extraFiles: [ - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css')), - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js')), - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html')) + extraFileResources: [ + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css'))), + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js'))), + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html'))) ], filePattern: '*.js' }); @@ -654,11 +683,12 @@ suite('FileSearchEngine', () => { test('Files: extraFiles only (with include)', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: [], - extraFiles: [ - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css')), - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js')), - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html')) + extraFileResources: [ + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css'))), + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js'))), + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html'))) ], filePattern: '*.*', includePattern: { '**/*.css': true } @@ -682,11 +712,12 @@ suite('FileSearchEngine', () => { test('Files: extraFiles only (with exclude)', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: [], - extraFiles: [ - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css')), - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js')), - path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html')) + extraFileResources: [ + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css'))), + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js'))), + URI.file(path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html'))) ], filePattern: '*.*', excludePattern: { '**/*.css': true } @@ -707,9 +738,10 @@ suite('FileSearchEngine', () => { test('Files: no dupes in nested folders', function (done: () => void) { this.timeout(testTimeout); let engine = new FileSearchEngine({ + type: QueryType.File, folderQueries: [ { folder: EXAMPLES_FIXTURES }, - { folder: path.join(EXAMPLES_FIXTURES, 'subfolder') } + { folder: joinPath(EXAMPLES_FIXTURES, 'subfolder') } ], filePattern: 'subfile.txt' }); @@ -739,14 +771,22 @@ suite('FileWalker', () => { const file0 = './more/file.txt'; const file1 = './examples/subfolder/subfile.txt'; - const walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/something': true } }); + const walker = new FileWalker({ + type: QueryType.File, + folderQueries: ROOT_FOLDER_QUERY, + excludePattern: { '**/something': true } + }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', /*isRipgrep=*/false, (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 walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/subfolder': true } }); + const walker = new FileWalker({ + type: QueryType.File, + folderQueries: ROOT_FOLDER_QUERY, + excludePattern: { '**/subfolder': true } + }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', /*isRipgrep=*/false, (err2, stdout2) => { assert.equal(err2, null); @@ -764,9 +804,9 @@ suite('FileWalker', () => { return; } - const folderQueries: IFolderSearch[] = [ + const folderQueries: IFolderQuery[] = [ { - folder: TEST_FIXTURES, + folder: URI.file(TEST_FIXTURES), excludePattern: { '**/subfolder': true } } ]; @@ -774,7 +814,7 @@ suite('FileWalker', () => { const file0 = './more/file.txt'; const file1 = './examples/subfolder/subfile.txt'; - const walker = new FileWalker({ folderQueries }); + const walker = new FileWalker({ type: QueryType.File, folderQueries }); const cmd1 = walker.spawnFindCmd(folderQueries[0]); walker.readStdout(cmd1, 'utf8', /*isRipgrep=*/false, (err1, stdout1) => { assert.equal(err1, null); @@ -795,7 +835,7 @@ suite('FileWalker', () => { const file1 = './examples/small.js'; const file2 = './more/file.txt'; - const walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/something': true } }); + const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/something': true } }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', /*isRipgrep=*/false, (err1, stdout1) => { assert.equal(err1, null); @@ -803,7 +843,7 @@ suite('FileWalker', () => { assert.notStrictEqual(stdout1.split('\n').indexOf(file1), -1, stdout1); assert.notStrictEqual(stdout1.split('\n').indexOf(file2), -1, stdout1); - const walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '{**/examples,**/more}': true } }); + const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '{**/examples,**/more}': true } }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', /*isRipgrep=*/false, (err2, stdout2) => { assert.equal(err2, null); @@ -825,14 +865,14 @@ suite('FileWalker', () => { const file0 = './examples/company.js'; const file1 = './examples/subfolder/subfile.txt'; - const walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/examples/something': true } }); + const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/examples/something': true } }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', /*isRipgrep=*/false, (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 walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/examples/subfolder': true } }); + const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/examples/subfolder': true } }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', /*isRipgrep=*/false, (err2, stdout2) => { assert.equal(err2, null); @@ -853,14 +893,14 @@ suite('FileWalker', () => { const file0 = './examples/subfolder/subfile.txt'; const file1 = './examples/subfolder/anotherfolder/anotherfile.txt'; - const walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/subfolder/something': true } }); + const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/subfolder/something': true } }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', /*isRipgrep=*/false, (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 walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/subfolder/anotherfolder': true } }); + const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/subfolder/anotherfolder': true } }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', /*isRipgrep=*/false, (err2, stdout2) => { assert.equal(err2, null); @@ -881,14 +921,14 @@ suite('FileWalker', () => { const file0 = './examples/company.js'; const file1 = './examples/subfolder/subfile.txt'; - const walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { 'examples/something': true } }); + const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { 'examples/something': true } }); const cmd1 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd1, 'utf8', /*isRipgrep=*/false, (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 walker = new FileWalker({ folderQueries: ROOT_FOLDER_QUERY, excludePattern: { 'examples/subfolder': true } }); + const walker = new FileWalker({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { 'examples/subfolder': true } }); const cmd2 = walker.spawnFindCmd(TEST_ROOT_FOLDER); walker.readStdout(cmd2, 'utf8', /*isRipgrep=*/false, (err2, stdout2) => { assert.equal(err2, null); @@ -917,6 +957,7 @@ suite('FileWalker', () => { ]; const walker = new FileWalker({ + type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, excludePattern: { '**/subfolder/anotherfolder': true, diff --git a/src/vs/workbench/services/search/test/node/searchService.test.ts b/src/vs/workbench/services/search/test/node/searchService.test.ts index 8b98983ca3c..f8544972b2b 100644 --- a/src/vs/workbench/services/search/test/node/searchService.test.ts +++ b/src/vs/workbench/services/search/test/node/searchService.test.ts @@ -8,20 +8,20 @@ import * as path from 'path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; -import { IProgress, ISearchEngineStats, IFileSearchStats } from 'vs/platform/search/common/search'; +import { URI } from 'vs/base/common/uri'; +import { IFileQuery, IFileSearchStats, IFolderQuery, IProgress, ISearchEngineStats, QueryType } from 'vs/platform/search/common/search'; import { SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService'; +import { IRawFileMatch, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from 'vs/workbench/services/search/node/search'; import { DiskSearch } from 'vs/workbench/services/search/node/searchService'; -import { IFolderSearch, IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; -import { ISearchEngine, IRawFileMatch, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchProgressItem, ISerializedSearchComplete, ISerializedSearchSuccess } from 'vs/workbench/services/search/node/search'; const TEST_FOLDER_QUERIES = [ - { folder: path.normalize('/some/where') } + { folder: URI.file(path.normalize('/some/where')) } ]; const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); -const MULTIROOT_QUERIES: IFolderSearch[] = [ - { folder: path.join(TEST_FIXTURES, 'examples') }, - { folder: path.join(TEST_FIXTURES, 'more') } +const MULTIROOT_QUERIES: IFolderQuery[] = [ + { folder: URI.file(path.join(TEST_FIXTURES, 'examples')) }, + { folder: URI.file(path.join(TEST_FIXTURES, 'more')) } ]; const stats: ISearchEngineStats = { @@ -38,7 +38,7 @@ class TestSearchEngine implements ISearchEngine { private isCanceled = false; - constructor(private result: () => IRawFileMatch, public config?: IRawSearch) { + constructor(private result: () => IRawFileMatch, public config?: IFileQuery) { TestSearchEngine.last = this; } @@ -76,7 +76,8 @@ const testTimeout = 5000; suite('SearchService', () => { - const rawSearch: IRawSearch = { + const rawSearch: IFileQuery = { + type: QueryType.File, folderQueries: TEST_FOLDER_QUERIES, filePattern: 'a' }; @@ -108,7 +109,7 @@ suite('SearchService', () => { } }; - await service.doFileSearch(Engine, rawSearch, cb); + await service.doFileSearchWithEngine(Engine, rawSearch, cb, null, 0); return assert.strictEqual(results, 5); }); @@ -130,7 +131,7 @@ suite('SearchService', () => { } }; - await service.doFileSearch(Engine, rawSearch, cb, undefined, 10); + await service.doFileSearchWithEngine(Engine, rawSearch, cb, undefined, 10); assert.deepStrictEqual(results, [10, 10, 5]); }); @@ -141,12 +142,12 @@ suite('SearchService', () => { const Engine = TestSearchEngine.bind(null, () => i-- && rawMatch); const service = new RawSearchService(); - function fileSearch(config: IRawSearch, batchSize: number): Event { + function fileSearch(config: IFileQuery, batchSize: number): Event { let promise: CancelablePromise; const emitter = new Emitter({ onFirstListenerAdd: () => { - promise = createCancelablePromise(token => service.doFileSearch(Engine, config, p => emitter.fire(p), token, batchSize) + promise = createCancelablePromise(token => service.doFileSearchWithEngine(Engine, config, p => emitter.fire(p), token, batchSize) .then(c => emitter.fire(c), err => emitter.fire({ type: 'error', error: err }))); }, onLastListenerRemove: () => { @@ -172,7 +173,8 @@ suite('SearchService', () => { this.timeout(testTimeout); const service = new RawSearchService(); - const query: IRawSearch = { + const query: IFileQuery = { + type: QueryType.File, folderQueries: MULTIROOT_QUERIES, maxResults: 1, includePattern: { @@ -189,7 +191,8 @@ suite('SearchService', () => { this.timeout(testTimeout); const service = new RawSearchService(); - const query: IRawSearch = { + const query: IFileQuery = { + type: QueryType.File, folderQueries: MULTIROOT_QUERIES, exists: true, includePattern: { @@ -224,7 +227,8 @@ suite('SearchService', () => { } }; - await service.doFileSearch(Engine, { + await service.doFileSearchWithEngine(Engine, { + type: QueryType.File, folderQueries: TEST_FOLDER_QUERIES, filePattern: 'bb', sortByScore: true, @@ -251,7 +255,8 @@ suite('SearchService', () => { assert.fail(JSON.stringify(value)); } }; - await service.doFileSearch(Engine, { + await service.doFileSearchWithEngine(Engine, { + type: QueryType.File, folderQueries: TEST_FOLDER_QUERIES, filePattern: 'a', sortByScore: true, @@ -280,7 +285,8 @@ suite('SearchService', () => { assert.fail(JSON.stringify(value)); } }; - return service.doFileSearch(Engine, { + return service.doFileSearchWithEngine(Engine, { + type: QueryType.File, folderQueries: TEST_FOLDER_QUERIES, filePattern: 'b', sortByScore: true, @@ -298,7 +304,8 @@ suite('SearchService', () => { } }; try { - const complete = await service.doFileSearch(Engine, { + const complete = await service.doFileSearchWithEngine(Engine, { + type: QueryType.File, folderQueries: TEST_FOLDER_QUERIES, filePattern: 'bc', sortByScore: true, @@ -325,7 +332,8 @@ suite('SearchService', () => { assert.fail(JSON.stringify(value)); } }; - const complete = await service.doFileSearch(Engine, { + const complete = await service.doFileSearchWithEngine(Engine, { + type: QueryType.File, folderQueries: TEST_FOLDER_QUERIES, filePattern: 'bc', sortByScore: 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 1d151a4bb6a..b71f32306de 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -11,11 +11,7 @@ import * as glob from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IFolderQuery, ITextQuery, QueryType } from 'vs/platform/search/common/search'; -import { FileWalker } from 'vs/workbench/services/search/node/fileSearch'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; -import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/legacy/textSearch'; -import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/legacy/textSearchWorkerProvider'; -import { rawSearchQuery } from 'vs/workbench/services/search/node/rawSearchService'; +import { LegacyTextSearchService } from 'vs/workbench/services/search/node/legacy/rawLegacyTextSearchService'; import { ISerializedFileMatch } from 'vs/workbench/services/search/node/search'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; @@ -36,31 +32,20 @@ const MULTIROOT_QUERIES: IFolderQuery[] = [ { folder: URI.file(MORE_FIXTURES) } ]; -const textSearchWorkerProvider = new TextSearchWorkerProvider(); +function doLegacySearchTest(config: ITextQuery, expectedResultCount: number | Function): TPromise { + const engine = new LegacyTextSearchService(); -function doLegacySearchTest(config: IRawSearch, expectedResultCount: number | Function): TPromise { - return new TPromise((resolve, reject) => { - let engine = new TextSearchEngine(config, new FileWalker({ ...config, useRipgrep: false }), textSearchWorkerProvider); - - let c = 0; - engine.search((result) => { - if (result) { - c += countAll(result); - } - }, () => { }, (error) => { - try { - assert.ok(!error); - if (typeof expectedResultCount === 'function') { - assert(expectedResultCount(c)); - } else { - assert.equal(c, expectedResultCount, 'legacy'); - } - } catch (e) { - reject(e); - } - - resolve(undefined); - }); + let c = 0; + return engine.textSearch(config, (result) => { + if (result && Array.isArray(result)) { + c += countAll(result); + } + }, null).then(() => { + if (typeof expectedResultCount === 'function') { + assert(expectedResultCount(c)); + } else { + assert.equal(c, expectedResultCount, 'legacy'); + } }); } @@ -83,8 +68,7 @@ function doRipgrepSearchTest(query: ITextQuery, expectedResultCount: number | Fu } function doSearchTest(query: ITextQuery, expectedResultCount: number) { - const legacyQuery = rawSearchQuery(query); - return doLegacySearchTest(legacyQuery, expectedResultCount) + return doLegacySearchTest(query, expectedResultCount) .then(() => doRipgrepSearchTest(query, expectedResultCount)); } @@ -256,7 +240,7 @@ suite('Search-integration', function () { // (Legacy) search can go over the maxResults because it doesn't trim the results from its worker processes to the exact max size. // But the worst-case scenario should be 2*max-1 - return doLegacySearchTest(rawSearchQuery(config), count => count < maxResults * 2) + return doLegacySearchTest(config, count => count < maxResults * 2) .then(() => doRipgrepSearchTest(config, maxResults)); });