Search provider - implement FileIndexProvider

This commit is contained in:
Rob Lourens
2018-07-25 20:48:18 -07:00
parent 9999dac541
commit d36a3d2395
19 changed files with 899 additions and 2069 deletions

View File

@@ -16,6 +16,7 @@ import { IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchComplet
import * as vscode from 'vscode';
import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol';
import { toDisposable } from 'vs/base/common/lifecycle';
import { IInternalFileMatch, QueryGlobTester, resolvePatternsForProvider, IDirectoryTree, IDirectoryEntry, FileIndexSearchManager } from 'vs/workbench/api/node/extHostSearch.fileIndex';
export interface ISchemeTransformer {
transformOutgoing(scheme: string): string;
@@ -24,15 +25,18 @@ export interface ISchemeTransformer {
export class ExtHostSearch implements ExtHostSearchShape {
private readonly _proxy: MainThreadSearchShape;
private readonly _searchProvider = new Map<number, vscode.SearchProvider>();
private readonly _fileSearchProvider = new Map<number, vscode.SearchProvider>();
private readonly _textSearchProvider = new Map<number, vscode.TextSearchProvider>();
private readonly _fileIndexProvider = new Map<number, vscode.FileIndexProvider>();
private _handlePool: number = 0;
private _fileSearchManager: FileSearchManager;
private _fileIndexSearchManager: FileIndexSearchManager;
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _extfs = extfs) {
this._proxy = mainContext.getProxy(MainContext.MainThreadSearch);
this._fileSearchManager = new FileSearchManager();
this._fileIndexSearchManager = new FileIndexSearchManager();
}
private _transformScheme(scheme: string): string {
@@ -42,12 +46,12 @@ export class ExtHostSearch implements ExtHostSearchShape {
return scheme;
}
registerSearchProvider(scheme: string, provider: vscode.SearchProvider) {
registerFileSearchProvider(scheme: string, provider: vscode.SearchProvider) {
const handle = this._handlePool++;
this._searchProvider.set(handle, provider);
this._proxy.$registerSearchProvider(handle, this._transformScheme(scheme));
this._fileSearchProvider.set(handle, provider);
this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme));
return toDisposable(() => {
this._searchProvider.delete(handle);
this._fileSearchProvider.delete(handle);
this._proxy.$unregisterProvider(handle);
});
}
@@ -55,27 +59,44 @@ export class ExtHostSearch implements ExtHostSearchShape {
registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider) {
const handle = this._handlePool++;
this._textSearchProvider.set(handle, provider);
this._proxy.$registerSearchProvider(handle, this._transformScheme(scheme));
this._proxy.$registerTextSearchProvider(handle, this._transformScheme(scheme));
return toDisposable(() => {
this._searchProvider.delete(handle);
this._textSearchProvider.delete(handle);
this._proxy.$unregisterProvider(handle);
});
}
$provideFileSearchResults(handle: number, session: number, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
const provider = this._searchProvider.get(handle);
if (!provider.provideFileSearchResults) {
return TPromise.as(undefined);
}
const query = reviveQuery(rawQuery);
return this._fileSearchManager.fileSearch(query, provider, progress => {
this._proxy.$handleFileMatch(handle, session, progress.map(p => p.resource));
registerFileIndexProvider(scheme: string, provider: vscode.FileIndexProvider) {
const handle = this._handlePool++;
this._fileIndexProvider.set(handle, provider);
this._proxy.$registerFileIndexProvider(handle, this._transformScheme(scheme));
return toDisposable(() => {
this._fileSearchProvider.delete(handle);
this._proxy.$unregisterProvider(handle); // TODO@roblou - unregisterFileIndexProvider
});
}
$provideFileSearchResults(handle: number, session: number, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
const provider = this._fileSearchProvider.get(handle);
const query = reviveQuery(rawQuery);
if (provider) {
return this._fileSearchManager.fileSearch(query, provider, progress => {
this._proxy.$handleFileMatch(handle, session, progress.map(p => p.resource));
});
} else {
const indexProvider = this._fileIndexProvider.get(handle);
if (indexProvider) {
return this._fileIndexSearchManager.fileSearch(query, indexProvider, progress => {
this._proxy.$handleFileMatch(handle, session, progress.map(p => p.resource));
});
} else {
throw new Error('something went wrong');
}
}
}
$clearCache(handle: number, cacheKey: string): TPromise<void> {
const provider = this._searchProvider.get(handle);
const provider = this._fileSearchProvider.get(handle);
if (!provider.clearCache) {
return TPromise.as(undefined);
}
@@ -96,22 +117,6 @@ export class ExtHostSearch implements ExtHostSearchShape {
}
}
/**
* Computes the patterns that the provider handles. Discards sibling clauses and 'false' patterns
*/
function resolvePatternsForProvider(globalPattern: glob.IExpression, folderPattern: glob.IExpression): string[] {
const merged = {
...(globalPattern || {}),
...(folderPattern || {})
};
return Object.keys(merged)
.filter(key => {
const value = merged[key];
return typeof value === 'boolean' && value;
});
}
function reviveQuery(rawQuery: IRawSearchQuery): ISearchQuery {
return {
...rawQuery,
@@ -264,107 +269,6 @@ class BatchedCollector<T> {
}
}
interface IDirectoryEntry {
base: URI;
relativePath: string;
basename: string;
}
interface IDirectoryTree {
rootEntries: IDirectoryEntry[];
pathToEntries: { [relativePath: string]: IDirectoryEntry[] };
}
interface IInternalFileMatch {
base: URI;
relativePath?: string; // Not present for extraFiles or absolute path matches
basename: string;
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, hasSibling?: (name: string) => boolean): boolean {
if (this._parsedExcludeExpression && this._parsedExcludeExpression(testPath, basename, hasSibling)) {
return false;
}
if (this._parsedIncludeExpression && !this._parsedIncludeExpression(testPath, basename, hasSibling)) {
return false;
}
return true;
}
/**
* Guaranteed async.
*/
public includedInQuery(testPath: string, basename?: string, hasSibling?: (name: string) => boolean | TPromise<boolean>): TPromise<boolean> {
const excludeP = this._parsedExcludeExpression ?
TPromise.as(this._parsedExcludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
TPromise.wrap(false);
return excludeP.then(excluded => {
if (excluded) {
return false;
}
return this._parsedIncludeExpression ?
TPromise.as(this._parsedIncludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
TPromise.wrap(true);
}).then(included => {
return included;
});
}
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 TextSearchEngine {
private activeCancellationTokens = new Set<CancellationTokenSource>();