mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-11 04:17:01 -05:00
Merge pull request #53536 from Microsoft/joao/search-progress-events
Use events instead of PPromise for search IPC
This commit is contained in:
@@ -25,7 +25,7 @@ import { IProgress, IUncachedSearchStats } from 'vs/platform/search/common/searc
|
||||
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import * as flow from 'vs/base/node/flow';
|
||||
import { IRawFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEngine, IFolderSearch } from './search';
|
||||
import { IRawFileMatch, IRawSearch, ISearchEngine, IFolderSearch, ISerializedSearchSuccess } from './search';
|
||||
import { spawnRipgrepCmd } from './ripgrepFileSearch';
|
||||
import { rgErrorMsgForDisplay } from './ripgrepTextSearch';
|
||||
|
||||
@@ -721,9 +721,10 @@ export class Engine implements ISearchEngine<IRawFileMatch> {
|
||||
this.walker = new FileWalker(config);
|
||||
}
|
||||
|
||||
public search(onResult: (result: IRawFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void {
|
||||
public search(onResult: (result: IRawFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void {
|
||||
this.walker.walk(this.folderQueries, this.extraFiles, onResult, onProgress, (err: Error, isLimitHit: boolean) => {
|
||||
done(err, {
|
||||
type: 'success',
|
||||
limitHit: isLimitHit,
|
||||
stats: this.walker.getStats()
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import { join, sep } from 'path';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { PPromise, TPromise } from 'vs/base/common/winjs.base';
|
||||
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, IProgress } from 'vs/platform/search/common/search';
|
||||
@@ -19,10 +19,14 @@ import { Engine as FileSearchEngine, FileWalker } from 'vs/workbench/services/se
|
||||
import { RipgrepEngine } from 'vs/workbench/services/search/node/ripgrepTextSearch';
|
||||
import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/textSearch';
|
||||
import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/textSearchWorkerProvider';
|
||||
import { IFileSearchProgressItem, IRawFileMatch, IRawSearch, IRawSearchService, ISearchEngine, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ITelemetryEvent } from './search';
|
||||
import { IFileSearchProgressItem, IRawFileMatch, IRawSearch, IRawSearchService, ISearchEngine, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ITelemetryEvent, ISerializedSearchSuccess } from './search';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
gracefulFs.gracefulify(fs);
|
||||
|
||||
type IProgressCallback = (p: ISerializedSearchProgressItem) => void;
|
||||
type IFileProgressCallback = (p: IFileSearchProgressItem) => void;
|
||||
|
||||
export class SearchService implements IRawSearchService {
|
||||
|
||||
private static readonly BATCH_SIZE = 512;
|
||||
@@ -31,29 +35,52 @@ export class SearchService implements IRawSearchService {
|
||||
|
||||
private textSearchWorkerProvider: TextSearchWorkerProvider;
|
||||
|
||||
private telemetryPipe: (event: ITelemetryEvent) => void;
|
||||
private _onTelemetry = new Emitter<ITelemetryEvent>();
|
||||
readonly onTelemetry: Event<ITelemetryEvent> = this._onTelemetry.event;
|
||||
|
||||
public fileSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
|
||||
return this.doFileSearch(FileSearchEngine, config, SearchService.BATCH_SIZE);
|
||||
public fileSearch(config: IRawSearch, batchSize = SearchService.BATCH_SIZE): Event<ISerializedSearchProgressItem | ISerializedSearchComplete> {
|
||||
let promise: TPromise<ISerializedSearchSuccess>;
|
||||
|
||||
const emitter = new Emitter<ISerializedSearchProgressItem | ISerializedSearchComplete>({
|
||||
onFirstListenerAdd: () => {
|
||||
promise = this.doFileSearch(FileSearchEngine, config, p => emitter.fire(p), batchSize)
|
||||
.then(c => emitter.fire(c), err => emitter.fire({ type: 'error', error: err }));
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
promise.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
public textSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
|
||||
return config.useRipgrep ?
|
||||
this.ripgrepTextSearch(config) :
|
||||
this.legacyTextSearch(config);
|
||||
public textSearch(config: IRawSearch): Event<ISerializedSearchProgressItem | ISerializedSearchComplete> {
|
||||
let promise: TPromise<ISerializedSearchSuccess>;
|
||||
|
||||
const emitter = new Emitter<ISerializedSearchProgressItem | ISerializedSearchComplete>({
|
||||
onFirstListenerAdd: () => {
|
||||
promise = (config.useRipgrep ? this.ripgrepTextSearch(config, p => emitter.fire(p)) : this.legacyTextSearch(config, p => emitter.fire(p)))
|
||||
.then(c => emitter.fire(c), err => emitter.fire({ type: 'error', error: err }));
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
promise.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
public ripgrepTextSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
|
||||
private ripgrepTextSearch(config: IRawSearch, progressCallback: IProgressCallback): TPromise<ISerializedSearchSuccess> {
|
||||
config.maxFilesize = MAX_FILE_SIZE;
|
||||
let engine = new RipgrepEngine(config);
|
||||
|
||||
return new PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>((c, e, p) => {
|
||||
return new TPromise<ISerializedSearchSuccess>((c, e) => {
|
||||
// Use BatchedCollector to get new results to the frontend every 2s at least, until 50 results have been returned
|
||||
const collector = new BatchedCollector<ISerializedFileMatch>(SearchService.BATCH_SIZE, p);
|
||||
const collector = new BatchedCollector<ISerializedFileMatch>(SearchService.BATCH_SIZE, progressCallback);
|
||||
engine.search((match) => {
|
||||
collector.addItem(match, match.numMatches);
|
||||
}, (message) => {
|
||||
p(message);
|
||||
progressCallback(message);
|
||||
}, (error, stats) => {
|
||||
collector.flush();
|
||||
|
||||
@@ -68,7 +95,7 @@ export class SearchService implements IRawSearchService {
|
||||
});
|
||||
}
|
||||
|
||||
public legacyTextSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
|
||||
private legacyTextSearch(config: IRawSearch, progressCallback: IProgressCallback): TPromise<ISerializedSearchComplete> {
|
||||
if (!this.textSearchWorkerProvider) {
|
||||
this.textSearchWorkerProvider = new TextSearchWorkerProvider();
|
||||
}
|
||||
@@ -86,75 +113,75 @@ export class SearchService implements IRawSearchService {
|
||||
}),
|
||||
this.textSearchWorkerProvider);
|
||||
|
||||
return this.doTextSearch(engine, SearchService.BATCH_SIZE);
|
||||
return this.doTextSearch(engine, progressCallback, SearchService.BATCH_SIZE);
|
||||
}
|
||||
|
||||
public doFileSearch(EngineClass: { new(config: IRawSearch): ISearchEngine<IRawFileMatch>; }, config: IRawSearch, batchSize?: number): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
|
||||
doFileSearch(EngineClass: { new(config: IRawSearch): ISearchEngine<IRawFileMatch>; }, config: IRawSearch, progressCallback: IProgressCallback, batchSize?: number): TPromise<ISerializedSearchSuccess> {
|
||||
const fileProgressCallback: IFileProgressCallback = progress => {
|
||||
if (Array.isArray(progress)) {
|
||||
progressCallback(progress.map(m => this.rawMatchToSearchItem(m)));
|
||||
} else if ((<IRawFileMatch>progress).relativePath) {
|
||||
progressCallback(this.rawMatchToSearchItem(<IRawFileMatch>progress));
|
||||
} else {
|
||||
progressCallback(<IProgress>progress);
|
||||
}
|
||||
};
|
||||
|
||||
if (config.sortByScore) {
|
||||
let sortedSearch = this.trySortedSearchFromCache(config);
|
||||
let sortedSearch = this.trySortedSearchFromCache(config, fileProgressCallback);
|
||||
if (!sortedSearch) {
|
||||
const walkerConfig = config.maxResults ? objects.assign({}, config, { maxResults: null }) : config;
|
||||
const engine = new EngineClass(walkerConfig);
|
||||
sortedSearch = this.doSortedSearch(engine, config);
|
||||
sortedSearch = this.doSortedSearch(engine, config, progressCallback, fileProgressCallback);
|
||||
}
|
||||
|
||||
return new PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>((c, e, p) => {
|
||||
return new TPromise<ISerializedSearchSuccess>((c, e) => {
|
||||
process.nextTick(() => { // allow caller to register progress callback first
|
||||
sortedSearch.then(([result, rawMatches]) => {
|
||||
const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch));
|
||||
this.sendProgress(serializedMatches, p, batchSize);
|
||||
this.sendProgress(serializedMatches, progressCallback, batchSize);
|
||||
c(result);
|
||||
}, e, p);
|
||||
}, e);
|
||||
});
|
||||
}, () => {
|
||||
sortedSearch.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
let searchPromise: PPromise<void, IFileSearchProgressItem>;
|
||||
return new PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>((c, e, p) => {
|
||||
const engine = new EngineClass(config);
|
||||
searchPromise = this.doSearch(engine, batchSize)
|
||||
.then(c, e, progress => {
|
||||
if (Array.isArray(progress)) {
|
||||
p(progress.map(m => this.rawMatchToSearchItem(m)));
|
||||
} else if ((<IRawFileMatch>progress).relativePath) {
|
||||
p(this.rawMatchToSearchItem(<IRawFileMatch>progress));
|
||||
} else {
|
||||
p(<IProgress>progress);
|
||||
}
|
||||
});
|
||||
}, () => {
|
||||
searchPromise.cancel();
|
||||
});
|
||||
const engine = new EngineClass(config);
|
||||
|
||||
return this.doSearch(engine, fileProgressCallback, batchSize);
|
||||
}
|
||||
|
||||
private rawMatchToSearchItem(match: IRawFileMatch): ISerializedFileMatch {
|
||||
return { path: match.base ? join(match.base, match.relativePath) : match.relativePath };
|
||||
}
|
||||
|
||||
private doSortedSearch(engine: ISearchEngine<IRawFileMatch>, config: IRawSearch): PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IProgress> {
|
||||
let searchPromise: PPromise<void, IFileSearchProgressItem>;
|
||||
let allResultsPromise = new PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IFileSearchProgressItem>((c, e, p) => {
|
||||
private doSortedSearch(engine: ISearchEngine<IRawFileMatch>, config: IRawSearch, progressCallback: IProgressCallback, fileProgressCallback: IFileProgressCallback): TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]> {
|
||||
let searchPromise: TPromise<void>;
|
||||
const emitter = new Emitter<IFileSearchProgressItem>();
|
||||
|
||||
let allResultsPromise = new TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]>((c, e) => {
|
||||
let results: IRawFileMatch[] = [];
|
||||
searchPromise = this.doSearch(engine, -1)
|
||||
|
||||
const innerProgressCallback: IFileProgressCallback = progress => {
|
||||
if (Array.isArray(progress)) {
|
||||
results = progress;
|
||||
} else {
|
||||
fileProgressCallback(progress);
|
||||
emitter.fire(progress);
|
||||
}
|
||||
};
|
||||
|
||||
searchPromise = this.doSearch(engine, innerProgressCallback, -1)
|
||||
.then(result => {
|
||||
c([result, results]);
|
||||
if (this.telemetryPipe) {
|
||||
// __GDPR__TODO__ classify event
|
||||
this.telemetryPipe({
|
||||
eventName: 'fileSearch',
|
||||
data: result.stats
|
||||
});
|
||||
}
|
||||
}, e, progress => {
|
||||
if (Array.isArray(progress)) {
|
||||
results = progress;
|
||||
} else {
|
||||
p(progress);
|
||||
}
|
||||
});
|
||||
// __GDPR__TODO__ classify event
|
||||
this._onTelemetry.fire({
|
||||
eventName: 'fileSearch',
|
||||
data: result.stats
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
searchPromise.cancel();
|
||||
});
|
||||
@@ -162,7 +189,10 @@ export class SearchService implements IRawSearchService {
|
||||
let cache: Cache;
|
||||
if (config.cacheKey) {
|
||||
cache = this.getOrCreateCache(config.cacheKey);
|
||||
cache.resultsToSearchCache[config.filePattern] = allResultsPromise;
|
||||
cache.resultsToSearchCache[config.filePattern] = {
|
||||
promise: allResultsPromise,
|
||||
event: emitter.event
|
||||
};
|
||||
allResultsPromise.then(null, err => {
|
||||
delete cache.resultsToSearchCache[config.filePattern];
|
||||
});
|
||||
@@ -170,7 +200,7 @@ export class SearchService implements IRawSearchService {
|
||||
}
|
||||
|
||||
let chained: TPromise<void>;
|
||||
return new PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IProgress>((c, e, p) => {
|
||||
return new TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]>((c, e) => {
|
||||
chained = allResultsPromise.then(([result, results]) => {
|
||||
const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null);
|
||||
const unsortedResultTime = Date.now();
|
||||
@@ -179,14 +209,15 @@ export class SearchService implements IRawSearchService {
|
||||
const sortedResultTime = Date.now();
|
||||
|
||||
c([{
|
||||
type: 'success',
|
||||
stats: objects.assign({}, result.stats, {
|
||||
unsortedResultTime,
|
||||
sortedResultTime
|
||||
}),
|
||||
limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults
|
||||
}, sortedResults]);
|
||||
} as ISerializedSearchSuccess, sortedResults]);
|
||||
});
|
||||
}, e, p);
|
||||
}, e);
|
||||
}, () => {
|
||||
chained.cancel();
|
||||
});
|
||||
@@ -200,17 +231,17 @@ export class SearchService implements IRawSearchService {
|
||||
return this.caches[cacheKey] = new Cache();
|
||||
}
|
||||
|
||||
private trySortedSearchFromCache(config: IRawSearch): PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IProgress> {
|
||||
private trySortedSearchFromCache(config: IRawSearch, progressCallback: IFileProgressCallback): TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]> {
|
||||
const cache = config.cacheKey && this.caches[config.cacheKey];
|
||||
if (!cache) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cacheLookupStartTime = Date.now();
|
||||
const cached = this.getResultsFromCache(cache, config.filePattern);
|
||||
const cached = this.getResultsFromCache(cache, config.filePattern, progressCallback);
|
||||
if (cached) {
|
||||
let chained: TPromise<void>;
|
||||
return new PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IProgress>((c, e, p) => {
|
||||
return new TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]>((c, e) => {
|
||||
chained = cached.then(([result, results, cacheStats]) => {
|
||||
const cacheLookupResultTime = Date.now();
|
||||
return this.sortResults(config, results, cache.scorerCache)
|
||||
@@ -234,13 +265,14 @@ export class SearchService implements IRawSearchService {
|
||||
}
|
||||
c([
|
||||
{
|
||||
type: 'success',
|
||||
limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults,
|
||||
stats: stats
|
||||
},
|
||||
} as ISerializedSearchSuccess,
|
||||
sortedResults
|
||||
]);
|
||||
});
|
||||
}, e, p);
|
||||
}, e);
|
||||
}, () => {
|
||||
chained.cancel();
|
||||
});
|
||||
@@ -259,7 +291,7 @@ export class SearchService implements IRawSearchService {
|
||||
return arrays.topAsync(results, compare, config.maxResults, 10000);
|
||||
}
|
||||
|
||||
private sendProgress(results: ISerializedFileMatch[], progressCb: (batch: ISerializedFileMatch[]) => void, batchSize: number) {
|
||||
private sendProgress(results: ISerializedFileMatch[], progressCb: IProgressCallback, batchSize: number) {
|
||||
if (batchSize && batchSize > 0) {
|
||||
for (let i = 0; i < results.length; i += batchSize) {
|
||||
progressCb(results.slice(i, i + batchSize));
|
||||
@@ -269,10 +301,10 @@ export class SearchService implements IRawSearchService {
|
||||
}
|
||||
}
|
||||
|
||||
private getResultsFromCache(cache: Cache, searchValue: string): PPromise<[ISerializedSearchComplete, IRawFileMatch[], CacheStats], IProgress> {
|
||||
private getResultsFromCache(cache: Cache, searchValue: string, progressCallback: IFileProgressCallback): TPromise<[ISerializedSearchSuccess, IRawFileMatch[], CacheStats]> {
|
||||
// Find cache entries by prefix of search value
|
||||
const hasPathSep = searchValue.indexOf(sep) >= 0;
|
||||
let cached: PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IFileSearchProgressItem>;
|
||||
let cachedRow: CacheRow;
|
||||
let wasResolved: boolean;
|
||||
for (let previousSearch in cache.resultsToSearchCache) {
|
||||
|
||||
@@ -282,20 +314,25 @@ export class SearchService implements IRawSearchService {
|
||||
continue; // since a path character widens the search for potential more matches, require it in previous search too
|
||||
}
|
||||
|
||||
const c = cache.resultsToSearchCache[previousSearch];
|
||||
c.then(() => { wasResolved = false; });
|
||||
const row = cache.resultsToSearchCache[previousSearch];
|
||||
row.promise.then(() => { wasResolved = false; });
|
||||
wasResolved = true;
|
||||
cached = this.preventCancellation(c);
|
||||
cachedRow = {
|
||||
promise: this.preventCancellation(row.promise),
|
||||
event: row.event
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cached) {
|
||||
if (!cachedRow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PPromise<[ISerializedSearchComplete, IRawFileMatch[], CacheStats], IProgress>((c, e, p) => {
|
||||
cached.then(([complete, cachedEntries]) => {
|
||||
const listener = cachedRow.event(progressCallback);
|
||||
|
||||
return new TPromise<[ISerializedSearchSuccess, IRawFileMatch[], CacheStats]>((c, e) => {
|
||||
cachedRow.promise.then(([complete, cachedEntries]) => {
|
||||
const cacheFilterStartTime = Date.now();
|
||||
|
||||
// Pattern match on results
|
||||
@@ -317,21 +354,22 @@ export class SearchService implements IRawSearchService {
|
||||
cacheFilterStartTime: cacheFilterStartTime,
|
||||
cacheFilterResultCount: cachedEntries.length
|
||||
}]);
|
||||
}, e, p);
|
||||
}, e);
|
||||
}, () => {
|
||||
cached.cancel();
|
||||
cachedRow.promise.cancel();
|
||||
listener.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
private doTextSearch(engine: TextSearchEngine, batchSize: number): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
|
||||
return new PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>((c, e, p) => {
|
||||
private doTextSearch(engine: TextSearchEngine, progressCallback: IProgressCallback, batchSize: number): TPromise<ISerializedSearchSuccess> {
|
||||
return new TPromise<ISerializedSearchSuccess>((c, e) => {
|
||||
// Use BatchedCollector to get new results to the frontend every 2s at least, until 50 results have been returned
|
||||
const collector = new BatchedCollector<ISerializedFileMatch>(batchSize, p);
|
||||
const collector = new BatchedCollector<ISerializedFileMatch>(batchSize, progressCallback);
|
||||
engine.search((matches) => {
|
||||
const totalMatches = matches.reduce((acc, m) => acc + m.numMatches, 0);
|
||||
collector.addItems(matches, totalMatches);
|
||||
}, (progress) => {
|
||||
p(progress);
|
||||
progressCallback(progress);
|
||||
}, (error, stats) => {
|
||||
collector.flush();
|
||||
|
||||
@@ -346,28 +384,28 @@ export class SearchService implements IRawSearchService {
|
||||
});
|
||||
}
|
||||
|
||||
private doSearch(engine: ISearchEngine<IRawFileMatch>, batchSize?: number): PPromise<ISerializedSearchComplete, IFileSearchProgressItem> {
|
||||
return new PPromise<ISerializedSearchComplete, IFileSearchProgressItem>((c, e, p) => {
|
||||
private doSearch(engine: ISearchEngine<IRawFileMatch>, progressCallback: IFileProgressCallback, batchSize?: number): TPromise<ISerializedSearchSuccess> {
|
||||
return new TPromise<ISerializedSearchSuccess>((c, e) => {
|
||||
let batch: IRawFileMatch[] = [];
|
||||
engine.search((match) => {
|
||||
if (match) {
|
||||
if (batchSize) {
|
||||
batch.push(match);
|
||||
if (batchSize > 0 && batch.length >= batchSize) {
|
||||
p(batch);
|
||||
progressCallback(batch);
|
||||
batch = [];
|
||||
}
|
||||
} else {
|
||||
p(match);
|
||||
progressCallback(match);
|
||||
}
|
||||
}
|
||||
}, (progress) => {
|
||||
process.nextTick(() => {
|
||||
p(progress);
|
||||
progressCallback(progress);
|
||||
});
|
||||
}, (error, stats) => {
|
||||
if (batch.length) {
|
||||
p(batch);
|
||||
progressCallback(batch);
|
||||
}
|
||||
if (error) {
|
||||
e(error);
|
||||
@@ -385,19 +423,11 @@ export class SearchService implements IRawSearchService {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
public fetchTelemetry(): PPromise<void, ITelemetryEvent> {
|
||||
return new PPromise((c, e, p) => {
|
||||
this.telemetryPipe = p;
|
||||
}, () => {
|
||||
this.telemetryPipe = null;
|
||||
});
|
||||
}
|
||||
|
||||
private preventCancellation<C, P>(promise: PPromise<C, P>): PPromise<C, P> {
|
||||
return new PPromise<C, P>((c, e, p) => {
|
||||
private preventCancellation<C, P>(promise: TPromise<C>): TPromise<C> {
|
||||
return new TPromise<C>((c, e) => {
|
||||
// Allow for piled up cancellations to come through first.
|
||||
process.nextTick(() => {
|
||||
promise.then(c, e, p);
|
||||
promise.then(c, e);
|
||||
});
|
||||
}, () => {
|
||||
// Do not propagate.
|
||||
@@ -405,9 +435,14 @@ export class SearchService implements IRawSearchService {
|
||||
}
|
||||
}
|
||||
|
||||
interface CacheRow {
|
||||
promise: TPromise<[ISerializedSearchSuccess, IRawFileMatch[]]>;
|
||||
event: Event<IFileSearchProgressItem>;
|
||||
}
|
||||
|
||||
class Cache {
|
||||
|
||||
public resultsToSearchCache: { [searchValue: string]: PPromise<[ISerializedSearchComplete, IRawFileMatch[]], IFileSearchProgressItem>; } = Object.create(null);
|
||||
public resultsToSearchCache: { [searchValue: string]: CacheRow; } = Object.create(null);
|
||||
|
||||
public scorerCache: ScorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as encoding from 'vs/base/node/encoding';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { IProgress } from 'vs/platform/search/common/search';
|
||||
import { rgPath } from 'vscode-ripgrep';
|
||||
import { FileMatch, IFolderSearch, IRawSearch, ISerializedFileMatch, ISerializedSearchComplete, LineMatch } from './search';
|
||||
import { FileMatch, IFolderSearch, IRawSearch, ISerializedFileMatch, LineMatch, ISerializedSearchSuccess } from './search';
|
||||
|
||||
// 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');
|
||||
@@ -44,10 +44,11 @@ export class RipgrepEngine {
|
||||
}
|
||||
|
||||
// TODO@Rob - make promise-based once the old search is gone, and I don't need them to have matching interfaces anymore
|
||||
search(onResult: (match: ISerializedFileMatch) => void, onMessage: (message: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void {
|
||||
search(onResult: (match: ISerializedFileMatch) => void, onMessage: (message: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void {
|
||||
if (!this.config.folderQueries.length && !this.config.extraFiles.length) {
|
||||
process.removeListener('exit', this.killRgProcFn);
|
||||
done(null, {
|
||||
type: 'success',
|
||||
limitHit: false,
|
||||
stats: null
|
||||
});
|
||||
@@ -94,6 +95,7 @@ export class RipgrepEngine {
|
||||
this.cancel();
|
||||
process.removeListener('exit', this.killRgProcFn);
|
||||
done(null, {
|
||||
type: 'success',
|
||||
limitHit: true,
|
||||
stats: null
|
||||
});
|
||||
@@ -124,11 +126,13 @@ export class RipgrepEngine {
|
||||
process.removeListener('exit', this.killRgProcFn);
|
||||
if (stderr && !gotData && (displayMsg = rgErrorMsgForDisplay(stderr))) {
|
||||
done(new Error(displayMsg), {
|
||||
type: 'success',
|
||||
limitHit: false,
|
||||
stats: null
|
||||
});
|
||||
} else {
|
||||
done(null, {
|
||||
type: 'success',
|
||||
limitHit: false,
|
||||
stats: null
|
||||
});
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { PPromise, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IExpression } from 'vs/base/common/glob';
|
||||
import { IProgress, ILineMatch, IPatternInfo, ISearchStats } from 'vs/platform/search/common/search';
|
||||
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IFolderSearch {
|
||||
folder: string;
|
||||
@@ -41,10 +42,10 @@ export interface ITelemetryEvent {
|
||||
}
|
||||
|
||||
export interface IRawSearchService {
|
||||
fileSearch(search: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>;
|
||||
textSearch(search: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>;
|
||||
fileSearch(search: IRawSearch): Event<ISerializedSearchProgressItem | ISerializedSearchComplete>;
|
||||
textSearch(search: IRawSearch): Event<ISerializedSearchProgressItem | ISerializedSearchComplete>;
|
||||
clearCache(cacheKey: string): TPromise<void>;
|
||||
fetchTelemetry(): PPromise<void, ITelemetryEvent>;
|
||||
readonly onTelemetry: Event<ITelemetryEvent>;
|
||||
}
|
||||
|
||||
export interface IRawFileMatch {
|
||||
@@ -55,15 +56,37 @@ export interface IRawFileMatch {
|
||||
}
|
||||
|
||||
export interface ISearchEngine<T> {
|
||||
search: (onResult: (matches: T) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void) => void;
|
||||
search: (onResult: (matches: T) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void) => void;
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
export interface ISerializedSearchComplete {
|
||||
export interface ISerializedSearchSuccess {
|
||||
type: 'success';
|
||||
limitHit: boolean;
|
||||
stats: ISearchStats;
|
||||
}
|
||||
|
||||
export interface ISerializedSearchError {
|
||||
type: 'error';
|
||||
error: any;
|
||||
}
|
||||
|
||||
export type ISerializedSearchComplete = ISerializedSearchSuccess | ISerializedSearchError;
|
||||
|
||||
export function isSerializedSearchComplete(arg: ISerializedSearchProgressItem | ISerializedSearchComplete): arg is ISerializedSearchComplete {
|
||||
if ((arg as any).type === 'error') {
|
||||
return true;
|
||||
} else if ((arg as any).type === 'success') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isSerializedSearchSuccess(arg: ISerializedSearchComplete): arg is ISerializedSearchSuccess {
|
||||
return arg.type === 'success';
|
||||
}
|
||||
|
||||
export interface ISerializedFileMatch {
|
||||
path: string;
|
||||
lineMatches?: ILineMatch[];
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { PPromise, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IRawSearchService, IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ITelemetryEvent } from './search';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface ISearchChannel extends IChannel {
|
||||
call(command: 'fileSearch', search: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>;
|
||||
call(command: 'textSearch', search: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>;
|
||||
listen(event: 'telemetry'): Event<ITelemetryEvent>;
|
||||
listen(event: 'fileSearch', search: IRawSearch): Event<ISerializedSearchProgressItem | ISerializedSearchComplete>;
|
||||
listen(event: 'textSearch', search: IRawSearch): Event<ISerializedSearchProgressItem | ISerializedSearchComplete>;
|
||||
call(command: 'clearCache', cacheKey: string): TPromise<void>;
|
||||
call(command: 'fetchTelemetry'): PPromise<void, ITelemetryEvent>;
|
||||
call(command: string, arg: any): TPromise<any>;
|
||||
}
|
||||
|
||||
@@ -22,38 +22,38 @@ export class SearchChannel implements ISearchChannel {
|
||||
|
||||
constructor(private service: IRawSearchService) { }
|
||||
|
||||
listen<T>(event: string, arg?: any): Event<T> {
|
||||
throw new Error('No events');
|
||||
listen<T>(event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
case 'telemetry': return this.service.onTelemetry;
|
||||
case 'fileSearch': return this.service.fileSearch(arg);
|
||||
case 'textSearch': return this.service.textSearch(arg);
|
||||
}
|
||||
throw new Error('Event not found');
|
||||
}
|
||||
|
||||
call(command: string, arg?: any): TPromise<any> {
|
||||
switch (command) {
|
||||
case 'fileSearch': return this.service.fileSearch(arg);
|
||||
case 'textSearch': return this.service.textSearch(arg);
|
||||
case 'clearCache': return this.service.clearCache(arg);
|
||||
case 'fetchTelemetry': return this.service.fetchTelemetry();
|
||||
}
|
||||
return undefined;
|
||||
throw new Error('Call not found');
|
||||
}
|
||||
}
|
||||
|
||||
export class SearchChannelClient implements IRawSearchService {
|
||||
|
||||
get onTelemetry(): Event<ITelemetryEvent> { return this.channel.listen('telemetry'); }
|
||||
|
||||
constructor(private channel: ISearchChannel) { }
|
||||
|
||||
fileSearch(search: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
|
||||
return this.channel.call('fileSearch', search);
|
||||
fileSearch(search: IRawSearch): Event<ISerializedSearchProgressItem | ISerializedSearchComplete> {
|
||||
return this.channel.listen('fileSearch', search);
|
||||
}
|
||||
|
||||
textSearch(search: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
|
||||
return this.channel.call('textSearch', search);
|
||||
textSearch(search: IRawSearch): Event<ISerializedSearchProgressItem | ISerializedSearchComplete> {
|
||||
return this.channel.listen('textSearch', search);
|
||||
}
|
||||
|
||||
clearCache(cacheKey: string): TPromise<void> {
|
||||
return this.channel.call('clearCache', cacheKey);
|
||||
}
|
||||
|
||||
fetchTelemetry(): PPromise<void, ITelemetryEvent> {
|
||||
return this.channel.call('fetchTelemetry');
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import { IProgress, LineMatch, FileMatch, ISearchComplete, ISearchProgressItem,
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService, ITelemetryEvent } from './search';
|
||||
import { IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService, ITelemetryEvent, isSerializedSearchComplete, isSerializedSearchSuccess, ISerializedSearchSuccess } from './search';
|
||||
import { ISearchChannel, SearchChannelClient } from './searchIpc';
|
||||
import { IEnvironmentService, IDebugParams } from 'vs/platform/environment/common/environment';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
@@ -26,6 +26,7 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export class SearchService implements ISearchService {
|
||||
public _serviceBrand: any;
|
||||
@@ -320,14 +321,14 @@ export class DiskSearch implements ISearchResultProvider {
|
||||
const existingFolders = folderQueries.filter((q, index) => exists[index]);
|
||||
const rawSearch = this.rawSearchQuery(query, existingFolders);
|
||||
|
||||
let request: PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>;
|
||||
let event: Event<ISerializedSearchProgressItem | ISerializedSearchComplete>;
|
||||
if (query.type === QueryType.File) {
|
||||
request = this.raw.fileSearch(rawSearch);
|
||||
event = this.raw.fileSearch(rawSearch);
|
||||
} else {
|
||||
request = this.raw.textSearch(rawSearch);
|
||||
event = this.raw.textSearch(rawSearch);
|
||||
}
|
||||
|
||||
return DiskSearch.collectResults(request);
|
||||
return DiskSearch.collectResultsFromEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -372,7 +373,28 @@ export class DiskSearch implements ISearchResultProvider {
|
||||
return rawSearch;
|
||||
}
|
||||
|
||||
public static collectResults(request: PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>): PPromise<ISearchComplete, ISearchProgressItem> {
|
||||
public static collectResultsFromEvent(event: Event<ISerializedSearchProgressItem | ISerializedSearchComplete>): PPromise<ISearchComplete, ISearchProgressItem> {
|
||||
const promise = new PPromise<ISerializedSearchSuccess, ISerializedSearchProgressItem>((c, e, p) => {
|
||||
setTimeout(() => {
|
||||
const listener = event(ev => {
|
||||
if (isSerializedSearchComplete(ev)) {
|
||||
if (isSerializedSearchSuccess(ev)) {
|
||||
c(ev);
|
||||
} else {
|
||||
e(ev.error);
|
||||
}
|
||||
listener.dispose();
|
||||
} else {
|
||||
p(ev);
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
|
||||
return DiskSearch.collectResults(promise);
|
||||
}
|
||||
|
||||
public static collectResults(request: PPromise<ISerializedSearchSuccess, ISerializedSearchProgressItem>): PPromise<ISearchComplete, ISearchProgressItem> {
|
||||
let result: IFileMatch[] = [];
|
||||
return new PPromise<ISearchComplete, ISearchProgressItem>((c, e, p) => {
|
||||
request.done((complete) => {
|
||||
@@ -420,6 +442,8 @@ export class DiskSearch implements ISearchResultProvider {
|
||||
}
|
||||
|
||||
public fetchTelemetry(): PPromise<void, ITelemetryEvent> {
|
||||
return this.raw.fetchTelemetry();
|
||||
return new PPromise<void, ITelemetryEvent>((c, e, p) => {
|
||||
this.raw.onTelemetry(p);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IProgress } from 'vs/platform/search/common/search';
|
||||
import { FileWalker } from 'vs/workbench/services/search/node/fileSearch';
|
||||
|
||||
import { ISerializedFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEngine } from './search';
|
||||
import { ISerializedFileMatch, IRawSearch, ISearchEngine, ISerializedSearchSuccess } from './search';
|
||||
import { ISearchWorker } from './worker/searchWorkerIpc';
|
||||
import { ITextSearchWorkerProvider } from './textSearchWorkerProvider';
|
||||
|
||||
@@ -60,7 +60,7 @@ export class Engine implements ISearchEngine<ISerializedFileMatch[]> {
|
||||
});
|
||||
}
|
||||
|
||||
search(onResult: (match: ISerializedFileMatch[]) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void {
|
||||
search(onResult: (match: ISerializedFileMatch[]) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void {
|
||||
this.workers = this.workerProvider.getWorkers();
|
||||
this.initializeWorkers();
|
||||
|
||||
@@ -86,6 +86,7 @@ export class Engine implements ISearchEngine<ISerializedFileMatch[]> {
|
||||
if (!this.isDone && this.processedBytes === this.totalBytes && this.walkerIsDone) {
|
||||
this.isDone = true;
|
||||
done(this.walkerError, {
|
||||
type: 'success',
|
||||
limitHit: this.limitReached,
|
||||
stats: this.walker.getStats()
|
||||
});
|
||||
|
||||
@@ -9,9 +9,11 @@ import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { IProgress, IUncachedSearchStats } from 'vs/platform/search/common/search';
|
||||
import { ISearchEngine, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchComplete, IFolderSearch } from 'vs/workbench/services/search/node/search';
|
||||
import { ISearchEngine, IRawSearch, IRawFileMatch, ISerializedFileMatch, IFolderSearch, ISerializedSearchSuccess, ISerializedSearchProgressItem, ISerializedSearchComplete } from 'vs/workbench/services/search/node/search';
|
||||
import { SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService';
|
||||
import { DiskSearch } from 'vs/workbench/services/search/node/searchService';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
const TEST_FOLDER_QUERIES = [
|
||||
{ folder: path.normalize('/some/where') }
|
||||
@@ -44,12 +46,13 @@ class TestSearchEngine implements ISearchEngine<IRawFileMatch> {
|
||||
TestSearchEngine.last = this;
|
||||
}
|
||||
|
||||
public search(onResult: (match: IRawFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void {
|
||||
public search(onResult: (match: IRawFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void {
|
||||
const self = this;
|
||||
(function next() {
|
||||
process.nextTick(() => {
|
||||
if (self.isCanceled) {
|
||||
done(null, {
|
||||
type: 'success',
|
||||
limitHit: false,
|
||||
stats: stats
|
||||
});
|
||||
@@ -58,6 +61,7 @@ class TestSearchEngine implements ISearchEngine<IRawFileMatch> {
|
||||
const result = self.result();
|
||||
if (!result) {
|
||||
done(null, {
|
||||
type: 'success',
|
||||
limitHit: false,
|
||||
stats: stats
|
||||
});
|
||||
@@ -101,17 +105,17 @@ suite('SearchService', () => {
|
||||
const service = new RawSearchService();
|
||||
|
||||
let results = 0;
|
||||
return service.doFileSearch(Engine, rawSearch)
|
||||
.then(() => {
|
||||
assert.strictEqual(results, 5);
|
||||
}, null, value => {
|
||||
if (!Array.isArray(value)) {
|
||||
assert.deepStrictEqual(value, match);
|
||||
results++;
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
const cb: (p: ISerializedSearchProgressItem) => void = value => {
|
||||
if (!Array.isArray(value)) {
|
||||
assert.deepStrictEqual(value, match);
|
||||
results++;
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
|
||||
return service.doFileSearch(Engine, rawSearch, cb)
|
||||
.then(() => assert.strictEqual(results, 5));
|
||||
});
|
||||
|
||||
test('Batch results', function () {
|
||||
@@ -121,19 +125,20 @@ suite('SearchService', () => {
|
||||
const service = new RawSearchService();
|
||||
|
||||
const results = [];
|
||||
return service.doFileSearch(Engine, rawSearch, 10)
|
||||
.then(() => {
|
||||
assert.deepStrictEqual(results, [10, 10, 5]);
|
||||
}, null, value => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(m => {
|
||||
assert.deepStrictEqual(m, match);
|
||||
});
|
||||
results.push(value.length);
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
const cb: (p: ISerializedSearchProgressItem) => void = value => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(m => {
|
||||
assert.deepStrictEqual(m, match);
|
||||
});
|
||||
results.push(value.length);
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
|
||||
return service.doFileSearch(Engine, rawSearch, cb, 10).then(() => {
|
||||
assert.deepStrictEqual(results, [10, 10, 5]);
|
||||
});
|
||||
});
|
||||
|
||||
test('Collect batched results', function () {
|
||||
@@ -143,8 +148,24 @@ suite('SearchService', () => {
|
||||
const Engine = TestSearchEngine.bind(null, () => i-- && rawMatch);
|
||||
const service = new RawSearchService();
|
||||
|
||||
function fileSearch(config: IRawSearch, batchSize: number): Event<ISerializedSearchProgressItem | ISerializedSearchComplete> {
|
||||
let promise: TPromise<ISerializedSearchSuccess>;
|
||||
|
||||
const emitter = new Emitter<ISerializedSearchProgressItem | ISerializedSearchComplete>({
|
||||
onFirstListenerAdd: () => {
|
||||
promise = service.doFileSearch(Engine, config, p => emitter.fire(p), batchSize)
|
||||
.then(c => emitter.fire(c), err => emitter.fire({ type: 'error', error: err }));
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
promise.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
const progressResults = [];
|
||||
return DiskSearch.collectResults(service.doFileSearch(Engine, rawSearch, 10))
|
||||
return DiskSearch.collectResultsFromEvent(fileSearch(rawSearch, 10))
|
||||
.then(result => {
|
||||
assert.strictEqual(result.results.length, 25, 'Result');
|
||||
assert.strictEqual(progressResults.length, 25, 'Progress');
|
||||
@@ -167,7 +188,7 @@ suite('SearchService', () => {
|
||||
},
|
||||
};
|
||||
|
||||
return DiskSearch.collectResults(service.fileSearch(query))
|
||||
return DiskSearch.collectResultsFromEvent(service.fileSearch(query))
|
||||
.then(result => {
|
||||
assert.strictEqual(result.results.length, 1, 'Result');
|
||||
});
|
||||
@@ -186,7 +207,7 @@ suite('SearchService', () => {
|
||||
},
|
||||
};
|
||||
|
||||
return DiskSearch.collectResults(service.fileSearch(query))
|
||||
return DiskSearch.collectResultsFromEvent(service.fileSearch(query))
|
||||
.then(result => {
|
||||
assert.strictEqual(result.results.length, 0, 'Result');
|
||||
assert.ok(result.limitHit);
|
||||
@@ -206,20 +227,22 @@ suite('SearchService', () => {
|
||||
const service = new RawSearchService();
|
||||
|
||||
const results = [];
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'bb',
|
||||
sortByScore: true,
|
||||
maxResults: 2
|
||||
}, 1).then(() => {
|
||||
assert.notStrictEqual(typeof TestSearchEngine.last.config.maxResults, 'number');
|
||||
assert.deepStrictEqual(results, [path.normalize('/some/where/bbc'), path.normalize('/some/where/bab')]);
|
||||
}, null, value => {
|
||||
const cb = value => {
|
||||
if (Array.isArray(value)) {
|
||||
results.push(...value.map(v => v.path));
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'bb',
|
||||
sortByScore: true,
|
||||
maxResults: 2
|
||||
}, cb, 1).then(() => {
|
||||
assert.notStrictEqual(typeof TestSearchEngine.last.config.maxResults, 'number');
|
||||
assert.deepStrictEqual(results, [path.normalize('/some/where/bbc'), path.normalize('/some/where/bab')]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -230,23 +253,24 @@ suite('SearchService', () => {
|
||||
const service = new RawSearchService();
|
||||
|
||||
const results = [];
|
||||
const cb = value => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(m => {
|
||||
assert.deepStrictEqual(m, match);
|
||||
});
|
||||
results.push(value.length);
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'a',
|
||||
sortByScore: true,
|
||||
maxResults: 23
|
||||
}, 10)
|
||||
}, cb, 10)
|
||||
.then(() => {
|
||||
assert.deepStrictEqual(results, [10, 10, 3]);
|
||||
}, null, value => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(m => {
|
||||
assert.deepStrictEqual(m, match);
|
||||
});
|
||||
results.push(value.length);
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -263,37 +287,39 @@ suite('SearchService', () => {
|
||||
const service = new RawSearchService();
|
||||
|
||||
const results = [];
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'b',
|
||||
sortByScore: true,
|
||||
cacheKey: 'x'
|
||||
}, -1).then(complete => {
|
||||
assert.strictEqual(complete.stats.fromCache, false);
|
||||
assert.deepStrictEqual(results, [path.normalize('/some/where/bcb'), path.normalize('/some/where/bbc'), path.normalize('/some/where/aab')]);
|
||||
}, null, value => {
|
||||
const cb = value => {
|
||||
if (Array.isArray(value)) {
|
||||
results.push(...value.map(v => v.path));
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'b',
|
||||
sortByScore: true,
|
||||
cacheKey: 'x'
|
||||
}, cb, -1).then(complete => {
|
||||
assert.strictEqual(complete.stats.fromCache, false);
|
||||
assert.deepStrictEqual(results, [path.normalize('/some/where/bcb'), path.normalize('/some/where/bbc'), path.normalize('/some/where/aab')]);
|
||||
}).then(() => {
|
||||
const results = [];
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'bc',
|
||||
sortByScore: true,
|
||||
cacheKey: 'x'
|
||||
}, -1).then(complete => {
|
||||
assert.ok(complete.stats.fromCache);
|
||||
assert.deepStrictEqual(results, [path.normalize('/some/where/bcb'), path.normalize('/some/where/bbc')]);
|
||||
}, null, value => {
|
||||
const cb = value => {
|
||||
if (Array.isArray(value)) {
|
||||
results.push(...value.map(v => v.path));
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
};
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'bc',
|
||||
sortByScore: true,
|
||||
cacheKey: 'x'
|
||||
}, cb, -1).then(complete => {
|
||||
assert.ok(complete.stats.fromCache);
|
||||
assert.deepStrictEqual(results, [path.normalize('/some/where/bcb'), path.normalize('/some/where/bbc')]);
|
||||
}, null);
|
||||
}).then(() => {
|
||||
return service.clearCache('x');
|
||||
}).then(() => {
|
||||
@@ -304,20 +330,21 @@ suite('SearchService', () => {
|
||||
size: 3
|
||||
});
|
||||
const results = [];
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'bc',
|
||||
sortByScore: true,
|
||||
cacheKey: 'x'
|
||||
}, -1).then(complete => {
|
||||
assert.strictEqual(complete.stats.fromCache, false);
|
||||
assert.deepStrictEqual(results, [path.normalize('/some/where/bc')]);
|
||||
}, null, value => {
|
||||
const cb = value => {
|
||||
if (Array.isArray(value)) {
|
||||
results.push(...value.map(v => v.path));
|
||||
} else {
|
||||
assert.fail(JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
return service.doFileSearch(Engine, {
|
||||
folderQueries: TEST_FOLDER_QUERIES,
|
||||
filePattern: 'bc',
|
||||
sortByScore: true,
|
||||
cacheKey: 'x'
|
||||
}, cb, -1).then(complete => {
|
||||
assert.strictEqual(complete.stats.fromCache, false);
|
||||
assert.deepStrictEqual(results, [path.normalize('/some/where/bc')]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user