EH search - respect sibling clauses in text search

This commit is contained in:
Rob Lourens
2018-05-16 17:06:58 -07:00
parent 184486bdf3
commit 3de1e91694
2 changed files with 367 additions and 37 deletions

View File

@@ -5,6 +5,7 @@
'use strict';
import * as pfs from 'vs/base/node/pfs';
import * as extfs from 'vs/base/node/extfs';
import * as path from 'path';
import * as arrays from 'vs/base/common/arrays';
import { asWinJsPromise } from 'vs/base/common/async';
@@ -33,7 +34,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
private _fileSearchManager: FileSearchManager;
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _pfs = pfs) {
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _extfs = extfs, private _pfs = pfs) {
this._proxy = mainContext.getProxy(MainContext.MainThreadSearch);
this._fileSearchManager = new FileSearchManager(this._pfs);
}
@@ -78,7 +79,8 @@ export class ExtHostSearch implements ExtHostSearchShape {
});
}
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, query: IRawSearchQuery): TPromise<void> {
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, rawQuery: IRawSearchQuery): TPromise<void> {
const query = reviveQuery(rawQuery);
return TPromise.join(
query.folderQueries.map(fq => this.provideTextSearchResultsForFolder(handle, session, pattern, query, fq))
).then(
@@ -88,7 +90,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
});
}
private provideTextSearchResultsForFolder(handle: number, session: number, pattern: IPatternInfo, query: IRawSearchQuery, folderQuery: IFolderQuery<UriComponents>): TPromise<void> {
private provideTextSearchResultsForFolder(handle: number, session: number, pattern: IPatternInfo, query: ISearchQuery, folderQuery: IFolderQuery): TPromise<void> {
const provider = this._searchProvider.get(handle);
if (!provider.provideTextSearchResults) {
return TPromise.as(undefined);
@@ -106,15 +108,35 @@ export class ExtHostSearch implements ExtHostSearchShape {
encoding: query.fileEncoding
};
const queryTester = new QueryGlobTester(query, folderQuery);
const collector = new TextSearchResultsCollector(handle, session, this._proxy);
const folderStr = folderQuery.folder.fsPath;
const testingPs = [];
const progress = {
report: (data: vscode.TextSearchResult) => {
collector.add(data);
report: (result: vscode.TextSearchResult) => {
const relativePath = path.relative(folderStr, result.uri.fsPath);
testingPs.push(
queryTester.includedInQuery(relativePath, path.basename(relativePath), () => this.readdir(path.dirname(result.uri.fsPath))).then(
included => included && collector.add(result)));
}
};
return asWinJsPromise(token => provider.provideTextSearchResults(pattern, searchOptions, progress, token))
.then(() => TPromise.join(testingPs))
.then(() => collector.flush());
}
private readdir(dirname: string): TPromise<string[]> {
return new TPromise((resolve, reject) => {
this._extfs.readdir(dirname, (err, files) => {
if (err) {
return reject(err);
}
resolve(files);
});
});
}
}
/**
@@ -306,6 +328,83 @@ interface IInternalFileMatch {
size?: number;
}
class QueryGlobTester {
private _excludeExpression: glob.IExpression;
private _parsedExcludeExpression: glob.ParsedExpression;
private _parsedIncludeExpression: glob.ParsedExpression;
constructor(config: ISearchQuery, folderQuery: IFolderQuery) {
this._excludeExpression = {
...(config.excludePattern || {}),
...(folderQuery.excludePattern || {})
};
this._parsedExcludeExpression = glob.parse(this._excludeExpression);
// Empty includeExpression means include nothing, so no {} shortcuts
let includeExpression: glob.IExpression = config.includePattern;
if (folderQuery.includePattern) {
if (includeExpression) {
includeExpression = {
...includeExpression,
...folderQuery.includePattern
};
} else {
includeExpression = folderQuery.includePattern;
}
}
if (includeExpression) {
this._parsedIncludeExpression = glob.parse(includeExpression);
}
}
/**
* Guaranteed sync - siblingsFn should not return a promise.
*/
public includedInQuerySync(testPath: string, basename?: string, siblingsFn?: () => string[]): boolean {
if (this._parsedExcludeExpression && this._parsedExcludeExpression(testPath, basename, siblingsFn)) {
return false;
}
if (this._parsedIncludeExpression && !this._parsedIncludeExpression(testPath, basename, siblingsFn)) {
return false;
}
return true;
}
/**
* Guaranteed async.
*/
public async includedInQuery(testPath: string, basename?: string, siblingsFn?: () => string[] | TPromise<string[]>): TPromise<boolean> {
if (this._parsedExcludeExpression && await this._parsedExcludeExpression(testPath, basename, siblingsFn)) {
return false;
}
if (this._parsedIncludeExpression && !await this._parsedIncludeExpression(testPath, basename, siblingsFn)) {
return false;
}
return true;
}
public hasSiblingExcludeClauses(): boolean {
return hasSiblingClauses(this._excludeExpression);
}
}
function hasSiblingClauses(pattern: glob.IExpression): boolean {
for (let key in pattern) {
if (typeof pattern[key] !== 'boolean') {
return true;
}
}
return false;
}
class FileSearchEngine {
private filePattern: string;
private normalizedFilePatternLowercase: string;
@@ -418,12 +517,8 @@ class FileSearchEngine {
let filePatternSeen = false;
const tree = this.initDirectoryTree();
const folderExcludeExpression: glob.IExpression = {
...(this.config.excludePattern || {}),
...(fq.excludePattern || {})
};
const parsedFolderExcludeExpression = glob.parse(folderExcludeExpression);
const noSiblingsClauses = !hasSiblingClauses(folderExcludeExpression);
const queryTester = new QueryGlobTester(this.config, fq);
const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses();
const onProviderResult = (result: URI) => {
if (this.isCanceled) {
@@ -475,7 +570,7 @@ class FileSearchEngine {
}
}
this.matchDirectoryTree(tree, folderStr, parsedFolderExcludeExpression, onResult);
this.matchDirectoryTree(tree, folderStr, queryTester, onResult);
return null;
}).then(
() => {
@@ -536,7 +631,7 @@ class FileSearchEngine {
add(relativeFile);
}
private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, rootFolder: string, excludePattern: glob.ParsedExpression, onResult: (result: IInternalFileMatch) => void) {
private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, rootFolder: string, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) {
const self = this;
const filePattern = this.filePattern;
function matchDirectory(entries: IDirectoryEntry[]) {
@@ -549,7 +644,7 @@ class FileSearchEngine {
// If the user searches for the exact file name, we adjust the glob matching
// 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
if (excludePattern(relativePath, basename, () => filePattern !== basename ? entries.map(entry => entry.basename) : [])) {
if (!queryTester.includedInQuerySync(relativePath, basename, () => filePattern !== basename ? entries.map(entry => entry.basename) : [])) {
continue;
}
@@ -657,16 +752,6 @@ class FileSearchEngine {
}
}
function hasSiblingClauses(pattern: glob.IExpression): boolean {
for (let key in pattern) {
if (typeof pattern[key] !== 'boolean') {
return true;
}
}
return false;
}
interface ISearchComplete {
limitHit: boolean;
stats?: any;
@@ -678,8 +763,7 @@ class FileSearchManager {
private caches: { [cacheKey: string]: Cache; } = Object.create(null);
constructor(private _pfs: typeof pfs) {
}
constructor(private _pfs: typeof pfs) { }
public fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise<ISearchComplete, OneOrMore<IFileMatch>> {
if (config.sortByScore) {