mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-23 18:19:12 +01:00
EH search - respect sibling clauses in text search
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user