diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index 14ac2479756..12cc9fbdb18 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -25,8 +25,9 @@ export const ISearchService = createDecorator('searchService'); */ export interface ISearchService { _serviceBrand: any; - search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise; - extendQuery(query: ISearchQuery): void; + textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise; + fileSearch(query: IFileQuery, token?: CancellationToken): TPromise; + extendQuery(query: ITextQuery | IFileQuery): void; clearCache(cacheKey: string): TPromise; registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable; } @@ -56,7 +57,8 @@ export const enum SearchProviderType { } export interface ISearchResultProvider { - search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise; + textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise; + fileSearch(query: IFileQuery, token?: CancellationToken): TPromise; clearCache(cacheKey: string): TPromise; } @@ -67,47 +69,52 @@ export interface IFolderQuery { fileEncoding?: string; disregardIgnoreFiles?: boolean; disregardGlobalIgnoreFiles?: boolean; + ignoreSymlinks?: boolean; } -export interface ICommonQueryOptions { +export interface ICommonQueryProps { + folderQueries?: IFolderQuery[]; + includePattern?: glob.IExpression; + excludePattern?: glob.IExpression; extraFileResources?: U[]; - filePattern?: string; // file search only - fileEncoding?: string; + + useRipgrep?: boolean; maxResults?: number; + usingSearchPaths?: boolean; +} + +export interface IFileQueryProps extends ICommonQueryProps { + type: QueryType.File; + filePattern?: string; + + // TODO: Remove this! + disregardExcludeSettings?: boolean; + /** * If true no results will be returned. Instead `limitHit` will indicate if at least one result exists or not. - * * Currently does not work with queries including a 'siblings clause'. */ exists?: boolean; sortByScore?: boolean; cacheKey?: string; - useRipgrep?: boolean; - disregardIgnoreFiles?: boolean; - disregardGlobalIgnoreFiles?: boolean; - disregardExcludeSettings?: boolean; - ignoreSymlinks?: boolean; - maxFileSize?: number; - previewOptions?: ITextSearchPreviewOptions; } -export interface IQueryOptions extends ICommonQueryOptions { - excludePattern?: string; - includePattern?: string; -} - -export interface ISearchQueryProps extends ICommonQueryOptions { - type: QueryType; - - excludePattern?: glob.IExpression; - includePattern?: glob.IExpression; +export interface ITextQueryProps extends ICommonQueryProps { + type: QueryType.Text; contentPattern?: IPatternInfo; - folderQueries?: IFolderQuery[]; - usingSearchPaths?: boolean; + + previewOptions?: ITextSearchPreviewOptions; + fileEncoding?: string; + maxFileSize?: number; } -export type ISearchQuery = ISearchQueryProps; -export type IRawSearchQuery = ISearchQueryProps; +export type IFileQuery = IFileQueryProps; +export type IRawFileQuery = IFileQueryProps; +export type ITextQuery = ITextQueryProps; +export type IRawTextQuery = ITextQueryProps; + +export type IRawQuery = IRawTextQuery | IRawFileQuery; +export type ISearchQuery = ITextQuery | IFileQuery; export const enum QueryType { File = 1, @@ -327,18 +334,18 @@ export function getExcludes(configuration: ISearchConfiguration): glob.IExpressi return allExcludes; } -export function pathIncludedInQuery(query: ISearchQuery, fsPath: string): boolean { - if (query.excludePattern && glob.match(query.excludePattern, fsPath)) { +export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: string): boolean { + if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath)) { return false; } - if (query.includePattern && !glob.match(query.includePattern, fsPath)) { + if (queryProps.includePattern && !glob.match(queryProps.includePattern, fsPath)) { return false; } // If searchPaths are being used, the extra file must be in a subfolder and match the pattern, if present - if (query.usingSearchPaths) { - return !!query.folderQueries && query.folderQueries.every(fq => { + if (queryProps.usingSearchPaths) { + return !!queryProps.folderQueries && queryProps.folderQueries.every(fq => { const searchPath = fq.folder.fsPath; if (paths.isEqualOrParent(fsPath, searchPath)) { return !fq.includePattern || !!glob.match(fq.includePattern, fsPath); diff --git a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts index f5e2f7996e9..a29485a24a8 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts @@ -8,7 +8,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, QueryType, SearchProviderType } from 'vs/platform/search/common/search'; +import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchResultProvider, ISearchService, QueryType, SearchProviderType, ITextQuery, IFileQuery } from 'vs/platform/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../node/extHost.protocol'; @@ -68,7 +68,7 @@ class SearchOperation { private static _idPool = 0; constructor( - readonly progress: (match: IFileMatch) => any, + readonly progress?: (match: IFileMatch) => any, readonly id: number = ++SearchOperation._idPool, readonly matches = new Map() ) { @@ -83,7 +83,9 @@ class SearchOperation { this.matches.set(match.resource.toString(), match); } - this.progress(match); + if (this.progress) { + this.progress(match); + } } } @@ -106,7 +108,15 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { dispose(this._registrations); } - search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): TPromise { + fileSearch(query: IFileQuery, token: CancellationToken = CancellationToken.None): TPromise { + return this.doSearch(query, null, token); + } + + textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): TPromise { + return this.doSearch(query, onProgress, token); + } + + doSearch(query: ITextQuery | IFileQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): TPromise { if (isFalsyOrEmpty(query.folderQueries)) { return TPromise.as(undefined); } @@ -116,7 +126,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { const searchP = query.type === QueryType.File ? this._proxy.$provideFileSearchResults(this._handle, search.id, query, token) - : this._proxy.$provideTextSearchResults(this._handle, search.id, query.contentPattern, query, token); + : this._proxy.$provideTextSearchResults(this._handle, search.id, query, token); return TPromise.wrap(searchP).then((result: ISearchCompleteStats) => { this._searches.delete(search.id); diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index fbf413cfb3b..c72506728de 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -13,12 +13,12 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IFolderQuery, IPatternInfo, IQueryOptions, ISearchConfiguration, ISearchProgressItem, ISearchQuery, ISearchService, QueryType } from 'vs/platform/search/common/search'; +import { IFolderQuery, IPatternInfo, ISearchConfiguration, ISearchProgressItem, ISearchService, QueryType, IFileQuery } from 'vs/platform/search/common/search'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; +import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; @@ -141,13 +141,17 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return !folderConfig.search.followSymlinks; }); - const query: ISearchQuery = { + // TODO replace wth QueryBuilder + folderQueries.forEach(fq => { + fq.ignoreSymlinks = ignoreSymlinks; + }); + + const query: IFileQuery = { folderQueries, type: QueryType.File, maxResults, disregardExcludeSettings: excludePatternOrDisregardExcludes === false, - useRipgrep, - ignoreSymlinks + useRipgrep }; if (typeof includePattern === 'string') { query.includePattern = { [includePattern]: true }; @@ -159,7 +163,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { this._searchService.extendQuery(query); - return this._searchService.search(query, token).then(result => { + return this._searchService.fileSearch(query, token).then(result => { return result.results.map(m => m.resource); }, err => { if (!isPromiseCanceledError(err)) { @@ -169,7 +173,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { }); } - $startTextSearch(pattern: IPatternInfo, options: IQueryOptions, requestId: number, token: CancellationToken): Thenable { + $startTextSearch(pattern: IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Thenable { const workspace = this._contextService.getWorkspace(); const folders = workspace.folders.map(folder => folder.uri); @@ -182,7 +186,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } }; - const search = this._searchService.search(query, token, onProgress).then( + const search = this._searchService.textSearch(query, token, onProgress).then( result => { return { limitHit: result.limitHit }; }, @@ -205,7 +209,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { exists: true }); - return this._searchService.search(query, token).then( + return this._searchService.fileSearch(query, token).then( result => { return result.limitHit; }, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 94d303c6bd0..17a1640f950 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -24,7 +24,7 @@ import { LabelRules } from 'vs/platform/label/common/label'; import { LogLevel } from 'vs/platform/log/common/log'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { IPickOptions, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IPatternInfo, IQueryOptions, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats } from 'vs/platform/search/common/search'; +import { IPatternInfo, IRawFileMatch2, IRawQuery, ISearchCompleteStats, IRawTextQuery } from 'vs/platform/search/common/search'; import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; @@ -41,6 +41,7 @@ import { IProgressOptions, IProgressStep } from 'vs/workbench/services/progress/ import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -482,7 +483,7 @@ export interface ExtHostUrlsShape { export interface MainThreadWorkspaceShape extends IDisposable { $startFileSearch(includePattern: string, includeFolder: URI, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Thenable; - $startTextSearch(query: IPatternInfo, options: IQueryOptions, requestId: number, token: CancellationToken): Thenable; + $startTextSearch(query: IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Thenable; $checkExists(includes: string[], token: CancellationToken): Thenable; $saveAll(includeUntitled?: boolean): Thenable; $updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable; @@ -707,9 +708,9 @@ export interface ExtHostFileSystemShape { } export interface ExtHostSearchShape { - $provideFileSearchResults(handle: number, session: number, query: IRawSearchQuery, token: CancellationToken): Thenable; + $provideFileSearchResults(handle: number, session: number, query: IRawQuery, token: CancellationToken): Thenable; + $provideTextSearchResults(handle: number, session: number, query: IRawTextQuery, token: CancellationToken): Thenable; $clearCache(cacheKey: string): Thenable; - $provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, query: IRawSearchQuery, token: CancellationToken): Thenable; } export interface ExtHostExtensionServiceShape { diff --git a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts index 239b7bd9c87..a74b9033fea 100644 --- a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts +++ b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts @@ -15,7 +15,7 @@ import * as strings from 'vs/base/common/strings'; import { URI } 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 { ICachedSearchStats, IFileIndexProviderStats, IFileMatch, IFileSearchStats, IFolderQuery, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search'; +import { ICachedSearchStats, IFileIndexProviderStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, ISearchCompleteStats } from 'vs/platform/search/common/search'; import { IDirectoryEntry, IDirectoryTree, IInternalFileMatch } from 'vs/workbench/services/search/node/fileSearchManager'; import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search'; import * as vscode from 'vscode'; @@ -43,7 +43,7 @@ export class FileIndexSearchEngine { private globalExcludePattern: glob.ParsedExpression; - constructor(private config: ISearchQuery, private provider: vscode.FileIndexProvider) { + constructor(private config: IFileQuery, private provider: vscode.FileIndexProvider) { this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || null; @@ -190,9 +190,9 @@ export class FileIndexSearchEngine { folder: fq.folder, excludes, includes, - useIgnoreFiles: !this.config.disregardIgnoreFiles, - useGlobalIgnoreFiles: !this.config.disregardGlobalIgnoreFiles, - followSymlinks: !this.config.ignoreSymlinks + useIgnoreFiles: !fq.disregardIgnoreFiles, + useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, + followSymlinks: !fq.ignoreSymlinks }; } @@ -304,7 +304,7 @@ export class FileIndexSearchManager { private readonly folderCacheKeys = new Map>(); - public fileSearch(config: ISearchQuery, provider: vscode.FileIndexProvider, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): TPromise { + public fileSearch(config: IFileQuery, provider: vscode.FileIndexProvider, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): TPromise { if (config.sortByScore) { let sortedSearch = this.trySortedSearchFromCache(config, token); if (!sortedSearch) { @@ -341,7 +341,7 @@ export class FileIndexSearchManager { }); } - private getFolderCacheKey(config: ISearchQuery): string { + private getFolderCacheKey(config: IFileQuery): string { const uri = config.folderQueries[0].folder.toString(); const folderCacheKey = config.cacheKey && `${uri}_${config.cacheKey}`; if (!this.folderCacheKeys.get(config.cacheKey)) { @@ -359,7 +359,7 @@ export class FileIndexSearchManager { }; } - private doSortedSearch(engine: FileIndexSearchEngine, config: ISearchQuery, token: CancellationToken): TPromise { + private doSortedSearch(engine: FileIndexSearchEngine, config: IFileQuery, token: CancellationToken): TPromise { let allResultsPromise = createCancelablePromise>(token => { return this.doSearch(engine, token); }); @@ -413,7 +413,7 @@ export class FileIndexSearchManager { return this.caches[cacheKey] = new Cache(); } - private trySortedSearchFromCache(config: ISearchQuery, token: CancellationToken): TPromise { + private trySortedSearchFromCache(config: IFileQuery, token: CancellationToken): TPromise { const folderCacheKey = this.getFolderCacheKey(config); const cache = folderCacheKey && this.caches[folderCacheKey]; if (!cache) { @@ -447,7 +447,7 @@ export class FileIndexSearchManager { return undefined; } - private sortResults(config: IRawSearchQuery, results: IInternalFileMatch[], scorerCache: ScorerCache, token: CancellationToken): TPromise { + private sortResults(config: IFileQuery, results: IInternalFileMatch[], 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 diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index f98fa001c00..79cde336717 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -9,10 +9,11 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import * as extfs from 'vs/base/node/extfs'; import { ILogService } from 'vs/platform/log/common/log'; -import { IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search'; +import { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISearchCompleteStats, ITextQuery } from 'vs/platform/search/common/search'; 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'; @@ -20,7 +21,6 @@ import { isSerializedFileMatch, isSerializedSearchComplete, isSerializedSearchSu import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol'; -import { IRawSearch, IFolderSearch } from 'vs/workbench/services/search/node/legacy/search'; export interface ISchemeTransformer { transformOutgoing(scheme: string): string; @@ -96,7 +96,7 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } - $provideFileSearchResults(handle: number, session: number, rawQuery: IRawSearchQuery, token: CancellationToken): Thenable { + $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: CancellationToken): Thenable { const query = reviveQuery(rawQuery); if (handle === this._internalFileSearchHandle) { return this.doInternalFileSearch(handle, session, query, token); @@ -115,24 +115,21 @@ export class ExtHostSearch implements ExtHostSearchShape { } } - private doInternalFileSearch(handle: number, session: number, rawQuery: ISearchQuery, token: CancellationToken): Thenable { + private doInternalFileSearch(handle: number, session: number, rawQuery: IFileQuery, token: CancellationToken): Thenable { return new Promise((resolve, reject) => { const query: IRawSearch = { folderQueries: [], - ignoreSymlinks: rawQuery.ignoreSymlinks, + ignoreSymlinks: rawQuery.folderQueries.some(fq => fq.ignoreSymlinks), filePattern: rawQuery.filePattern, excludePattern: rawQuery.excludePattern, includePattern: rawQuery.includePattern, - contentPattern: rawQuery.contentPattern, maxResults: rawQuery.maxResults, exists: rawQuery.exists, sortByScore: rawQuery.sortByScore, cacheKey: rawQuery.cacheKey, - maxFilesize: rawQuery.maxFileSize, useRipgrep: rawQuery.useRipgrep, - disregardIgnoreFiles: rawQuery.disregardIgnoreFiles, - previewOptions: rawQuery.previewOptions, - disregardGlobalIgnoreFiles: rawQuery.disregardGlobalIgnoreFiles + disregardIgnoreFiles: rawQuery.folderQueries.some(fq => fq.disregardIgnoreFiles), + disregardGlobalIgnoreFiles: rawQuery.folderQueries.some(fq => fq.disregardGlobalIgnoreFiles), }; query.folderQueries = rawQuery.folderQueries.map(fq => ({ disregardGlobalIgnoreFiles: fq.disregardGlobalIgnoreFiles, @@ -181,14 +178,14 @@ export class ExtHostSearch implements ExtHostSearchShape { return this._fileIndexSearchManager.clearCache(cacheKey); } - $provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, rawQuery: IRawSearchQuery, token: CancellationToken): Thenable { + $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: CancellationToken): Thenable { const provider = this._textSearchProvider.get(handle); if (!provider.provideTextSearchResults) { return TPromise.as(undefined); } const query = reviveQuery(rawQuery); - const engine = new TextSearchManager(pattern, query, provider, this._extfs); + const engine = new TextSearchManager(query, provider, this._extfs); return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); } } @@ -202,9 +199,9 @@ function registerEHProviders(extHostSearch: ExtHostSearch, logService: ILogServi } } -function reviveQuery(rawQuery: IRawSearchQuery): ISearchQuery { +function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { return { - ...rawQuery, + ...rawQuery, // TODO ...{ folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery), extraFileResources: rawQuery.extraFileResources && rawQuery.extraFileResources.map(components => URI.revive(components)) diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 08a022a9fad..01afd29ffc2 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -16,13 +16,14 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; -import { IQueryOptions, IRawFileMatch2 } from 'vs/platform/search/common/search'; +import { IRawFileMatch2 } from 'vs/platform/search/common/search'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Range, RelativePattern } from 'vs/workbench/api/node/extHostTypes'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IMainContext, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; function isFolderEqual(folderA: URI, folderB: URI): boolean { return isEqual(folderA, folderB, !isLinux); @@ -399,7 +400,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { } : options.previewOptions; - const queryOptions: IQueryOptions = { + const queryOptions: ITextQueryBuilderOptions = { ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, diff --git a/src/vs/workbench/parts/search/browser/openFileHandler.ts b/src/vs/workbench/parts/search/browser/openFileHandler.ts index 408b144a5e1..8c6d8ab843c 100644 --- a/src/vs/workbench/parts/search/browser/openFileHandler.ts +++ b/src/vs/workbench/parts/search/browser/openFileHandler.ts @@ -19,12 +19,12 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen'; -import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; +import { QueryBuilder, IFileQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IQueryOptions, ISearchService, IFileSearchStats, ISearchQuery, ISearchComplete } from 'vs/platform/search/common/search'; +import { ISearchService, IFileSearchStats, IFileQuery, ISearchComplete } from 'vs/platform/search/common/search'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IRange } from 'vs/editor/common/core/range'; @@ -171,7 +171,7 @@ export class OpenFileHandler extends QuickOpenHandler { return TPromise.wrap({ results: [{ resource: result }] }); } - return this.searchService.search(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token); + return this.searchService.fileSearch(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token); }).then(complete => { const results: QuickOpenEntry[] = []; @@ -200,8 +200,8 @@ export class OpenFileHandler extends QuickOpenHandler { return TPromise.as(null); } - private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): IQueryOptions { - const queryOptions: IQueryOptions = { + private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): IFileQueryBuilderOptions { + const queryOptions: IFileQueryBuilderOptions = { extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService), filePattern: query.value, cacheKey @@ -220,12 +220,12 @@ export class OpenFileHandler extends QuickOpenHandler { } onOpen(): void { - this.cacheState = new CacheState(cacheKey => this.cacheQuery(cacheKey), query => this.searchService.search(query), cacheKey => this.searchService.clearCache(cacheKey), this.cacheState); + this.cacheState = new CacheState(cacheKey => this.cacheQuery(cacheKey), query => this.searchService.fileSearch(query), cacheKey => this.searchService.clearCache(cacheKey), this.cacheState); this.cacheState.load(); } - private cacheQuery(cacheKey: string): ISearchQuery { - const options: IQueryOptions = { + private cacheQuery(cacheKey: string): IFileQuery { + const options: IFileQueryBuilderOptions = { extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService), filePattern: '', cacheKey: cacheKey, @@ -268,12 +268,12 @@ enum LoadingPhase { export class CacheState { private _cacheKey = defaultGenerator.nextId(); - private query: ISearchQuery; + private query: IFileQuery; private loadingPhase = LoadingPhase.Created; private promise: TPromise; - constructor(cacheQuery: (cacheKey: string) => ISearchQuery, private doLoad: (query: ISearchQuery) => TPromise, private doDispose: (cacheKey: string) => TPromise, private previous: CacheState) { + constructor(cacheQuery: (cacheKey: string) => IFileQuery, private doLoad: (query: IFileQuery) => TPromise, private doDispose: (cacheKey: string) => TPromise, private previous: CacheState) { this.query = cacheQuery(this._cacheKey); if (this.previous) { const current = objects.assign({}, this.query, { cacheKey: null }); diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index 81e64d0b38b..0d52f6c9fa5 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -33,7 +33,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, IQueryOptions, ISearchComplete, ISearchConfiguration, ISearchHistoryService, ISearchProgressItem, ISearchQuery, VIEW_ID, ISearchHistoryValues } from 'vs/platform/search/common/search'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchHistoryService, ISearchProgressItem, VIEW_ID, ISearchHistoryValues, ITextQuery } from 'vs/platform/search/common/search'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -49,7 +49,7 @@ import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLe import { SearchAccessibilityProvider, SearchDataSource, SearchFilter, SearchRenderer, SearchSorter, SearchTreeController } from 'vs/workbench/parts/search/browser/searchResultsView'; import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/parts/search/browser/searchWidget'; import * as Constants from 'vs/workbench/parts/search/common/constants'; -import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; +import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search'; import { FileMatch, FileMatchOrMatch, FolderMatch, IChangeEvent, ISearchWorkbenchService, Match, SearchModel } from 'vs/workbench/parts/search/common/searchModel'; @@ -1052,7 +1052,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { const charsPerLine = content.isRegExp ? 10000 : 250; - const options: IQueryOptions = { + const options: ITextQueryBuilderOptions = { extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService), maxResults: SearchView.MAX_TEXT_RESULTS, disregardIgnoreFiles: !useExcludesAndIgnoreFiles, @@ -1071,7 +1071,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.viewModel.searchResult.clear(); }; - let query: ISearchQuery; + let query: ITextQuery; try { query = this.queryBuilder.text(content, folderResources.map(folder => folder.uri), options); } catch (err) { @@ -1088,7 +1088,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { }, onQueryValidationError); } - private validateQuery(query: ISearchQuery): TPromise { + private validateQuery(query: ITextQuery): TPromise { // Validate folderQueries const folderQueriesExistP = query.folderQueries.map(fq => { @@ -1110,7 +1110,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { }); } - private onQueryTriggered(query: ISearchQuery, excludePatternText: string, includePatternText: string): void { + private onQueryTriggered(query: ITextQuery, excludePatternText: string, includePatternText: string): void { this.inputPatternExcludes.onSearchSubmit(); this.inputPatternIncludes.onSearchSubmit(); diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index e0b4b36eb17..d379cee892c 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; -import * as objects from 'vs/base/common/objects'; import * as collections from 'vs/base/common/collections'; -import * as strings from 'vs/base/common/strings'; import * as glob from 'vs/base/common/glob'; +import { untildify } from 'vs/base/common/labels'; +import * as objects from 'vs/base/common/objects'; import * as paths from 'vs/base/common/paths'; import * as resources from 'vs/base/common/resources'; +import * as strings from 'vs/base/common/strings'; import { URI as uri } from 'vs/base/common/uri'; -import { untildify } from 'vs/base/common/labels'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IPatternInfo, IQueryOptions, IFolderQuery, ISearchQuery, QueryType, ISearchConfiguration, getExcludes, pathIncludedInQuery } from 'vs/platform/search/common/search'; +import { isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; +import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; +import { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/platform/search/common/search'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; export interface ISearchPathPattern { searchPath: uri; @@ -29,6 +29,32 @@ export interface ISearchPathsResult { pattern?: glob.IExpression; } +export interface ICommonQueryBuilderOptions { + excludePattern?: string; + includePattern?: string; + extraFileResources?: uri[]; + + maxResults?: number; + useRipgrep?: boolean; + disregardIgnoreFiles?: boolean; + disregardGlobalIgnoreFiles?: boolean; + disregardExcludeSettings?: boolean; + ignoreSymlinks?: boolean; +} + +export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { + filePattern?: string; + exists?: boolean; + sortByScore?: boolean; + cacheKey?: string; +} + +export interface ITextQueryBuilderOptions extends ICommonQueryBuilderOptions { + previewOptions?: ITextSearchPreviewOptions; + fileEncoding?: string; + maxFileSize?: number; +} + export class QueryBuilder { constructor( @@ -37,81 +63,71 @@ export class QueryBuilder { @IEnvironmentService private environmentService: IEnvironmentService ) { } - public text(contentPattern: IPatternInfo, folderResources?: uri[], options?: IQueryOptions): ISearchQuery { - return this.query(QueryType.Text, contentPattern, folderResources, options); + text(contentPattern: IPatternInfo, folderResources?: uri[], options?: ITextQueryBuilderOptions): ITextQuery { + contentPattern.isCaseSensitive = this.isCaseSensitive(contentPattern); + contentPattern.isMultiline = this.isMultiline(contentPattern); + contentPattern.wordSeparators = this.configurationService.getValue().editor.wordSeparators; + + const commonQuery = this.commonQuery(folderResources, options); + return { + ...commonQuery, + type: QueryType.Text, + contentPattern, + previewOptions: options && options.previewOptions, + fileEncoding: options && options.fileEncoding, + maxFileSize: options && options.maxFileSize + }; } - public file(folderResources?: uri[], options?: IQueryOptions): ISearchQuery { - return this.query(QueryType.File, null, folderResources, options); + file(folderResources?: uri[], options?: IFileQueryBuilderOptions): IFileQuery { + const commonQuery = this.commonQuery(folderResources, options); + return { + ...commonQuery, + type: QueryType.File, + filePattern: options.filePattern + ? options.filePattern.trim() + : options.filePattern, + exists: options.exists, + sortByScore: options.sortByScore, + cacheKey: options.cacheKey + }; } - private query(type: QueryType, contentPattern?: IPatternInfo, folderResources?: uri[], options: IQueryOptions = {}): ISearchQuery { + private commonQuery(folderResources?: uri[], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps { let { searchPaths, pattern: includePattern } = this.parseSearchPaths(options.includePattern); let excludePattern = this.parseExcludePattern(options.excludePattern); // Build folderQueries from searchPaths, if given, otherwise folderResources - let folderQueries = folderResources && folderResources.map(uri => this.getFolderQueryForRoot(uri, type === QueryType.File, options)); + let folderQueries = folderResources && folderResources.map(uri => this.getFolderQueryForRoot(uri, options)); if (searchPaths && searchPaths.length) { const allRootExcludes = folderQueries && this.mergeExcludesFromFolderQueries(folderQueries); - folderQueries = searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath)); + folderQueries = searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath)); // TODO Rob if (allRootExcludes) { excludePattern = objects.mixin(excludePattern || Object.create(null), allRootExcludes); } } - // TODO@rob - see #37998 - const useIgnoreFiles = !folderResources || folderResources.every(folder => { - const folderConfig = this.configurationService.getValue({ resource: folder }); - return folderConfig.search.useIgnoreFiles; - }); - - const useGlobalIgnoreFiles = !folderResources || folderResources.every(folder => { - const folderConfig = this.configurationService.getValue({ resource: folder }); - return folderConfig.search.useGlobalIgnoreFiles; - }); - const useRipgrep = !folderResources || folderResources.every(folder => { const folderConfig = this.configurationService.getValue({ resource: folder }); return folderConfig.search.useRipgrep; }); - const ignoreSymlinks = !this.configurationService.getValue().search.followSymlinks; - - if (contentPattern) { - contentPattern.isCaseSensitive = this.isCaseSensitive(contentPattern); - contentPattern.isMultiline = this.isMultiline(contentPattern); - - contentPattern.wordSeparators = this.configurationService.getValue().editor.wordSeparators; - } - - const query: ISearchQuery = { - type, + const queryProps: ICommonQueryProps = { folderQueries: folderQueries || [], usingSearchPaths: !!(searchPaths && searchPaths.length), extraFileResources: options.extraFileResources, - filePattern: options.filePattern - ? options.filePattern.trim() - : options.filePattern, + excludePattern, includePattern, maxResults: options.maxResults, - sortByScore: options.sortByScore, - cacheKey: options.cacheKey, - contentPattern, - useRipgrep, - disregardIgnoreFiles: options.disregardIgnoreFiles || !useIgnoreFiles, - disregardGlobalIgnoreFiles: options.disregardGlobalIgnoreFiles || !useGlobalIgnoreFiles, - disregardExcludeSettings: options.disregardExcludeSettings, - ignoreSymlinks, - previewOptions: options.previewOptions, - exists: options.exists + useRipgrep }; // Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace - let extraFileResources = options.extraFileResources && options.extraFileResources.filter(extraFile => pathIncludedInQuery(query, extraFile.fsPath)); - query.extraFileResources = extraFileResources && extraFileResources.length ? extraFileResources : undefined; + let extraFileResources = options.extraFileResources && options.extraFileResources.filter(extraFile => pathIncludedInQuery(queryProps, extraFile.fsPath)); + queryProps.extraFileResources = extraFileResources && extraFileResources.length ? extraFileResources : undefined; - return query; + return queryProps; } /** @@ -235,7 +251,7 @@ export class QueryBuilder { }, Object.create(null)); } - private getExcludesForFolder(folderConfig: ISearchConfiguration, options: IQueryOptions): glob.IExpression | undefined { + private getExcludesForFolder(folderConfig: ISearchConfiguration, options: ICommonQueryBuilderOptions): glob.IExpression | undefined { return options.disregardExcludeSettings ? undefined : getExcludes(folderConfig); @@ -313,14 +329,15 @@ export class QueryBuilder { }; } - private getFolderQueryForRoot(folder: uri, perFolderUseIgnoreFiles: boolean, options?: IQueryOptions): IFolderQuery { + private getFolderQueryForRoot(folder: uri, options: ICommonQueryBuilderOptions): IFolderQuery { const folderConfig = this.configurationService.getValue({ resource: folder }); return { folder, excludePattern: this.getExcludesForFolder(folderConfig, options), fileEncoding: folderConfig.files && folderConfig.files.encoding, - disregardIgnoreFiles: perFolderUseIgnoreFiles ? !folderConfig.search.useIgnoreFiles : undefined, - disregardGlobalIgnoreFiles: perFolderUseIgnoreFiles ? !folderConfig.search.useGlobalIgnoreFiles : undefined + disregardIgnoreFiles: typeof options.disregardIgnoreFiles === 'boolean' ? options.disregardIgnoreFiles : !folderConfig.search.useIgnoreFiles, + disregardGlobalIgnoreFiles: typeof options.disregardGlobalIgnoreFiles === 'boolean' ? options.disregardGlobalIgnoreFiles : !folderConfig.search.useGlobalIgnoreFiles, + ignoreSymlinks: typeof options.ignoreSymlinks === 'boolean' ? options.ignoreSymlinks : !folderConfig.search.followSymlinks, }; } } diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index 095a46e2f52..2aa6b653660 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -21,7 +21,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/platform/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, TextSearchResult } from 'vs/platform/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchService, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, TextSearchResult, ITextQuery } from 'vs/platform/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -366,7 +366,7 @@ export class FolderMatch extends Disposable { private _unDisposedFileMatches: ResourceMap; private _replacingAll: boolean = false; - constructor(private _resource: URI | null, private _id: string, private _index: number, private _query: ISearchQuery, private _parent: SearchResult, private _searchModel: SearchModel, @IReplaceService private replaceService: IReplaceService, + constructor(private _resource: URI | null, private _id: string, private _index: number, private _query: ITextQuery, private _parent: SearchResult, private _searchModel: SearchModel, @IReplaceService private replaceService: IReplaceService, @IInstantiationService private instantiationService: IInstantiationService) { super(); this._fileMatches = new ResourceMap(); @@ -563,7 +563,7 @@ export class SearchResult extends Disposable { this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); } - public set query(query: ISearchQuery) { + public set query(query: ITextQuery) { // When updating the query we could change the roots, so ensure we clean up the old roots first. this.clear(); this._folderMatches = (query.folderQueries || []) @@ -574,7 +574,7 @@ export class SearchResult extends Disposable { this._otherFilesMatch = this.createFolderMatch(null, 'otherFiles', this._folderMatches.length + 1, query); } - private createFolderMatch(resource: URI | null, id: string, index: number, query: ISearchQuery): FolderMatch { + private createFolderMatch(resource: URI | null, id: string, index: number, query: ITextQuery): FolderMatch { const folderMatch = this.instantiationService.createInstance(FolderMatch, resource, id, index, query, this, this._searchModel); const disposable = folderMatch.onChange((event) => this._onChange.fire(event)); folderMatch.onDispose(() => disposable.dispose()); @@ -744,7 +744,7 @@ export class SearchResult extends Disposable { export class SearchModel extends Disposable { private _searchResult: SearchResult; - private _searchQuery: ISearchQuery | null = null; + private _searchQuery: ITextQuery | null = null; private _replaceActive: boolean = false; private _replaceString: string | null = null; private _replacePattern: ReplacePattern | null = null; @@ -787,7 +787,7 @@ export class SearchModel extends Disposable { return this._searchResult; } - public search(query: ISearchQuery, onProgress?: (result: ISearchProgressItem) => void): TPromise { + public search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): TPromise { this.cancelSearch(); this._searchQuery = query; @@ -798,7 +798,7 @@ export class SearchModel extends Disposable { this._replacePattern = new ReplacePattern(this._replaceString, this._searchQuery.contentPattern); const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); - const currentRequest = this.searchService.search(this._searchQuery, this.currentCancelTokenSource.token, p => { + const currentRequest = this.searchService.textSearch(this._searchQuery, this.currentCancelTokenSource.token, p => { progressEmitter.fire(); this.onSearchProgress(p); diff --git a/src/vs/workbench/parts/search/test/browser/openFileHandler.test.ts b/src/vs/workbench/parts/search/test/browser/openFileHandler.test.ts index 95b77c6a04f..9186a2bcefa 100644 --- a/src/vs/workbench/parts/search/test/browser/openFileHandler.test.ts +++ b/src/vs/workbench/parts/search/test/browser/openFileHandler.test.ts @@ -9,7 +9,7 @@ import * as objects from 'vs/base/common/objects'; import { TPromise } from 'vs/base/common/winjs.base'; import { CacheState } from 'vs/workbench/parts/search/browser/openFileHandler'; import { DeferredTPromise } from 'vs/base/test/common/utils'; -import { QueryType, ISearchQuery } from 'vs/platform/search/common/search'; +import { QueryType, IFileQuery } from 'vs/platform/search/common/search'; suite('CacheState', () => { @@ -178,16 +178,16 @@ suite('CacheState', () => { public loading: { [cacheKey: string]: DeferredTPromise } = {}; public disposing: { [cacheKey: string]: DeferredTPromise } = {}; - public baseQuery: ISearchQuery = { + public baseQuery: IFileQuery = { type: QueryType.File }; - public query(cacheKey: string): ISearchQuery { + public query(cacheKey: string): IFileQuery { this.cacheKeys.push(cacheKey); return objects.assign({ cacheKey: cacheKey }, this.baseQuery); } - public load(query: ISearchQuery): TPromise { + public load(query: IFileQuery): TPromise { const promise = new DeferredTPromise(); this.loading[query.cacheKey] = promise; return promise; diff --git a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts index 70ea4a59a83..a01c226dbd1 100644 --- a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts @@ -10,14 +10,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IFolderQuery, IPatternInfo, ISearchQuery, QueryType } from 'vs/platform/search/common/search'; +import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'vs/platform/search/common/search'; import { IWorkspaceContextService, toWorkspaceFolders, Workspace } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsResult, QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; -const DEFAULT_QUERY_PROPS = { useRipgrep: true, disregardIgnoreFiles: false, disregardGlobalIgnoreFiles: false }; +const DEFAULT_QUERY_PROPS = { useRipgrep: true }; suite('QueryBuilder', () => { const PATTERN_INFO: IPatternInfo = { pattern: 'a' }; @@ -51,7 +51,7 @@ suite('QueryBuilder', () => { test('simple text pattern', () => { assertEqualQueries( queryBuilder.text(PATTERN_INFO), - { + { folderQueries: [], contentPattern: PATTERN_INFO, type: QueryType.Text @@ -64,7 +64,7 @@ suite('QueryBuilder', () => { PATTERN_INFO, [ROOT_1_URI] ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI }], type: QueryType.Text @@ -87,7 +87,7 @@ suite('QueryBuilder', () => { PATTERN_INFO, [ROOT_1_URI] ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI, @@ -109,7 +109,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { includePattern: './bar' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: getUri(fixPath(paths.join(ROOT_1, 'bar'))) @@ -123,7 +123,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { includePattern: '.\\bar' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: getUri(fixPath(paths.join(ROOT_1, 'bar'))) @@ -149,7 +149,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { includePattern: './foo' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: getUri(paths.join(ROOT_1, 'foo')) @@ -188,7 +188,7 @@ suite('QueryBuilder', () => { PATTERN_INFO, [ROOT_1_URI, ROOT_2_URI, ROOT_3_URI] ), - { + { contentPattern: PATTERN_INFO, folderQueries: [ { folder: ROOT_1_URI, excludePattern: patternsToIExpression('foo/**/*.js') }, @@ -206,7 +206,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI, ROOT_2_URI, ROOT_3_URI], { includePattern: './root2/src' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [ { folder: getUri(paths.join(ROOT_2, 'src')) } @@ -224,7 +224,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { excludePattern: 'foo' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI @@ -237,16 +237,14 @@ suite('QueryBuilder', () => { test('file pattern trimming', () => { const content = 'content'; assertEqualQueries( - queryBuilder.text( - PATTERN_INFO, + queryBuilder.file( undefined, { filePattern: ` ${content} ` } ), - { + { folderQueries: [], - contentPattern: PATTERN_INFO, filePattern: content, - type: QueryType.Text + type: QueryType.File }); }); @@ -257,7 +255,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { excludePattern: './bar' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI @@ -272,7 +270,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { excludePattern: './bar/**/*.ts' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI @@ -287,7 +285,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { excludePattern: '.\\bar\\**\\*.ts' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI @@ -304,7 +302,7 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { extraFileResources: [getUri('/foo/bar.js')] } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI @@ -322,7 +320,7 @@ suite('QueryBuilder', () => { excludePattern: '*.js' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI @@ -340,7 +338,7 @@ suite('QueryBuilder', () => { includePattern: '*.txt' } ), - { + { contentPattern: PATTERN_INFO, folderQueries: [{ folder: ROOT_1_URI @@ -766,7 +764,7 @@ suite('QueryBuilder', () => { }); }); -function assertEqualQueries(actual: ISearchQuery, expected: ISearchQuery): void { +function assertEqualQueries(actual: ITextQuery | IFileQuery, expected: ITextQuery | IFileQuery): void { expected = { ...DEFAULT_QUERY_PROPS, ...expected @@ -781,8 +779,6 @@ function assertEqualQueries(actual: ISearchQuery, expected: ISearchQuery): void }; }; - delete actual.ignoreSymlinks; - // Avoid comparing URI objects, not a good idea if (expected.folderQueries) { assert.deepEqual(actual.folderQueries.map(folderQueryToCompareObject), expected.folderQueries.map(folderQueryToCompareObject)); diff --git a/src/vs/workbench/parts/search/test/common/searchModel.test.ts b/src/vs/workbench/parts/search/test/common/searchModel.test.ts index c54dad1e84f..920fb34e529 100644 --- a/src/vs/workbench/parts/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/parts/search/test/common/searchModel.test.ts @@ -71,7 +71,7 @@ suite('SearchModel', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModelService, stubModelService(instantiationService)); instantiationService.stub(ISearchService, {}); - instantiationService.stub(ISearchService, 'search', TPromise.as({ results: [] })); + instantiationService.stub(ISearchService, 'textSearch', TPromise.as({ results: [] })); }); teardown(() => { @@ -82,7 +82,7 @@ suite('SearchModel', () => { function searchServiceWithResults(results: IFileMatch[], complete: ISearchComplete | null = null): ISearchService { return { - search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise { + textSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise { return new TPromise(resolve => { process.nextTick(() => { results.forEach(onProgress); @@ -95,7 +95,7 @@ suite('SearchModel', () => { function searchServiceWithError(error: Error): ISearchService { return { - search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise { + textSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise { return new TPromise((resolve, reject) => { reject(error); }); @@ -105,7 +105,7 @@ suite('SearchModel', () => { function canceleableSearchService(tokenSource: CancellationTokenSource): ISearchService { return { - search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise { + textSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise { if (token) { token.onCancellationRequested(() => tokenSource.cancel()); } @@ -237,7 +237,7 @@ suite('SearchModel', () => { instantiationService.stub(ITelemetryService, 'publicLog', target1); let promise = new DeferredTPromise(); - instantiationService.stub(ISearchService, 'search', promise); + instantiationService.stub(ISearchService, 'textSearch', promise); let testObject = instantiationService.createInstance(SearchModel); let result = testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries }); diff --git a/src/vs/workbench/services/search/node/fileSearchManager.ts b/src/vs/workbench/services/search/node/fileSearchManager.ts index 8948187d9f0..7acdf19bdc3 100644 --- a/src/vs/workbench/services/search/node/fileSearchManager.ts +++ b/src/vs/workbench/services/search/node/fileSearchManager.ts @@ -11,7 +11,7 @@ import * as resources from 'vs/base/common/resources'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search'; +import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery } from 'vs/platform/search/common/search'; import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search'; import * as vscode from 'vscode'; @@ -48,7 +48,7 @@ class FileSearchEngine { private globalExcludePattern: glob.ParsedExpression; - constructor(private config: ISearchQuery, private provider: vscode.FileSearchProvider) { + constructor(private config: IFileQuery, private provider: vscode.FileSearchProvider) { this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || null; @@ -189,9 +189,9 @@ class FileSearchEngine { folder: fq.folder, excludes, includes, - useIgnoreFiles: !this.config.disregardIgnoreFiles, - useGlobalIgnoreFiles: !this.config.disregardGlobalIgnoreFiles, - followSymlinks: !this.config.ignoreSymlinks, + useIgnoreFiles: !fq.disregardIgnoreFiles, + useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, + followSymlinks: !fq.ignoreSymlinks, maxResults: this.config.maxResults }; } @@ -289,7 +289,7 @@ export class FileSearchManager { private static readonly BATCH_SIZE = 512; - fileSearch(config: ISearchQuery, provider: vscode.FileSearchProvider, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): TPromise { + fileSearch(config: IFileQuery, provider: vscode.FileSearchProvider, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): TPromise { const engine = new FileSearchEngine(config, provider); let resultCount = 0; diff --git a/src/vs/workbench/services/search/node/legacy/search.ts b/src/vs/workbench/services/search/node/legacy/search.ts index 1234f14b874..23d4710b031 100644 --- a/src/vs/workbench/services/search/node/legacy/search.ts +++ b/src/vs/workbench/services/search/node/legacy/search.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as glob from 'vs/base/common/glob'; -import { ITextSearchPreviewOptions, IPatternInfo } from 'vs/platform/search/common/search'; +import { IPatternInfo, ITextSearchPreviewOptions } from 'vs/platform/search/common/search'; export interface IFolderSearch { folder: string; diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 6e34a8be152..11cc30d1d17 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -17,12 +17,14 @@ import * as strings from 'vs/base/common/strings'; 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 } from 'vs/platform/search/common/search'; +import { ICachedSearchStats, IFileSearchStats, IProgress, IRawTextQuery, ITextQuery, IRawQuery, IFileQuery, IFolderQuery } 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); @@ -57,15 +59,16 @@ export class SearchService implements IRawSearchService { return emitter.event; } - public textSearch(config: IRawSearch): Event { + public textSearch(rawQuery: IRawTextQuery): Event { let promise: CancelablePromise; + const query = reviveQuery(rawQuery); const emitter = new Emitter({ onFirstListenerDidAdd: () => { promise = createCancelablePromise(token => { - return (config.useRipgrep ? - this.ripgrepTextSearch(config, p => emitter.fire(p), token) : - this.legacyTextSearchService.textSearch(config, p => emitter.fire(p), token)); + return (rawQuery.useRipgrep ? + this.ripgrepTextSearch(query, p => emitter.fire(p), token) : + this.legacyTextSearchService.textSearch(rawSearchQuery(query), p => emitter.fire(p), token)); }); promise.then( @@ -80,8 +83,8 @@ export class SearchService implements IRawSearchService { return emitter.event; } - private ripgrepTextSearch(config: IRawSearch, progressCallback: IProgressCallback, token: CancellationToken): Promise { - config.maxFilesize = MAX_FILE_SIZE; + private ripgrepTextSearch(config: ITextQuery, progressCallback: IProgressCallback, token: CancellationToken): Promise { + config.maxFileSize = MAX_FILE_SIZE; const engine = new TextSearchEngineAdapter(config); return new Promise((c, e) => { @@ -431,3 +434,61 @@ const FileMatchItemAccessor = new class implements IItemAccessor return match.relativePath; // e.g. some/path/to/file/myFile.txt } }; + +function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { + return { + ...rawQuery, // TODO + ...{ + folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery), + extraFileResources: rawQuery.extraFileResources && rawQuery.extraFileResources.map(components => URI.revive(components)) + } + }; +} + +function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolderQuery { + return { + ...rawFolderQuery, + 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/search.ts b/src/vs/workbench/services/search/node/search.ts index f5bc8e9bcc4..8b91c35fcb3 100644 --- a/src/vs/workbench/services/search/node/search.ts +++ b/src/vs/workbench/services/search/node/search.ts @@ -6,7 +6,7 @@ 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 } from 'vs/platform/search/common/search'; +import { IFileSearchStats, IFolderQuery, IProgress, ISearchEngineStats, ISearchQuery, ITextSearchResult, ITextSearchStats, IRawTextQuery } from 'vs/platform/search/common/search'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; @@ -17,7 +17,7 @@ export interface ITelemetryEvent { export interface IRawSearchService { fileSearch(search: IRawSearch): Event; - textSearch(search: IRawSearch): 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 49389688990..28770ed6811 100644 --- a/src/vs/workbench/services/search/node/searchIpc.ts +++ b/src/vs/workbench/services/search/node/searchIpc.ts @@ -8,10 +8,11 @@ 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 { 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: 'textSearch', search: IRawSearch): Event; + listen(event: 'textSearch', search: IRawTextQuery): Event; call(command: 'clearCache', cacheKey: string): TPromise; call(command: string, arg: any): TPromise; } @@ -44,7 +45,7 @@ export class SearchChannelClient implements IRawSearchService { return this.channel.listen('fileSearch', search); } - textSearch(search: IRawSearch): Event { + textSearch(search: IRawTextQuery): Event { return this.channel.listen('textSearch', search); } diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 8bed708bbe6..3a9cfc19414 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -9,11 +9,10 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ResourceMap, values, keys } from 'vs/base/common/map'; +import { keys, ResourceMap, values } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; 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 as uri } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import * as pfs from 'vs/base/node/pfs'; @@ -25,14 +24,14 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDebugParams, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; -import { FileMatch, ICachedSearchStats, IFileMatch, IFileSearchStats, IFolderQuery, IProgress, ISearchComplete, ISearchConfiguration, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType, SearchProviderType, TextSearchResult } from 'vs/platform/search/common/search'; +import { FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, ISearchComplete, ISearchConfiguration, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType, SearchProviderType, TextSearchResult } from 'vs/platform/search/common/search'; 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'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; export class SearchService extends Disposable implements ISearchService { public _serviceBrand: any; @@ -73,15 +72,9 @@ export class SearchService extends Disposable implements ISearchService { }); } - public extendQuery(query: ISearchQuery): void { + public extendQuery(query: IFileQuery): void { const configuration = this.configurationService.getValue(); - // Configuration: Encoding - if (!query.fileEncoding) { - const fileEncoding = configuration && configuration.files && configuration.files.encoding; - query.fileEncoding = fileEncoding; - } - // Configuration: File Excludes if (!query.disregardExcludeSettings) { const fileExcludes = objects.deepClone(configuration && configuration.files && configuration.files.exclude); @@ -95,7 +88,7 @@ export class SearchService extends Disposable implements ISearchService { } } - public search(query: ISearchQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): TPromise { + public textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): TPromise { // Get local results from dirty/untitled const localResults = this.getLocalResults(query); @@ -121,6 +114,14 @@ export class SearchService extends Disposable implements ISearchService { } }; + return this.doSearch(query, token, onProviderProgress); + } + + public fileSearch(query: IFileQuery, token?: CancellationToken): TPromise { + return this.doSearch(query, token); + } + + private doSearch(query: ITextQuery | IFileQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): TPromise { const schemesInQuery = this.getSchemesInQuery(query); const providerActivations: TPromise[] = [TPromise.wrap(null)]; @@ -135,7 +136,7 @@ export class SearchService extends Disposable implements ISearchService { return TPromise.wrapError(canceled()); } - return this.searchWithProviders(query, onProviderProgress, token); + return this.searchWithProviders(query, onProgress, token); }) .then(completes => { completes = completes.filter(c => !!c); @@ -157,35 +158,6 @@ export class SearchService extends Disposable implements ISearchService { return TPromise.wrapError(errs[0]); }); - const searchP = providerPromise.then(value => { - const values = [value]; - - const result: ISearchComplete = { - limitHit: false, - results: [], - stats: undefined - }; - - // TODO@joh - // sorting, disjunct results - for (const value of values) { - if (!value) { - continue; - } - // TODO@joh individual stats/limit - result.stats = value.stats || result.stats; - result.limitHit = value.limitHit || result.limitHit; - - for (const match of value.results) { - if (!localResults.has(match.resource)) { - result.results.push(match); - } - } - } - - return result; - }); - return new TPromise((resolve, reject) => { if (token) { token.onCancellationRequested(() => { @@ -193,7 +165,7 @@ export class SearchService extends Disposable implements ISearchService { }); } - searchP.then(resolve, reject); + providerPromise.then(resolve, reject); }); } @@ -228,14 +200,16 @@ export class SearchService extends Disposable implements ISearchService { } else if (!provider) { throw new Error('No search provider registered for scheme: ' + scheme); } else { - const oneSchemeQuery = { + const oneSchemeQuery: ISearchQuery = { ...query, ...{ folderQueries: schemeFQs } }; - searchPs.push(provider.search(oneSchemeQuery, onProviderProgress, token)); + searchPs.push(query.type === QueryType.File ? + provider.fileSearch(oneSchemeQuery, token) : + provider.textSearch(oneSchemeQuery, onProviderProgress, token)); } }); @@ -250,7 +224,9 @@ export class SearchService extends Disposable implements ISearchService { extraFileResources: diskSearchExtraFileResources }; - searchPs.push(this.diskSearch.search(diskSearchQuery, onProviderProgress, token)); + searchPs.push(diskSearchQuery.type === QueryType.File ? + this.diskSearch.fileSearch(diskSearchQuery, token) : + this.diskSearch.textSearch(diskSearchQuery, onProviderProgress, token)); } return TPromise.join(searchPs).then(completes => { @@ -351,7 +327,7 @@ export class SearchService extends Disposable implements ISearchService { } } - private getLocalResults(query: ISearchQuery): ResourceMap { + private getLocalResults(query: ITextQuery): ResourceMap { const localResults = new ResourceMap(); if (query.type === QueryType.Text) { @@ -403,18 +379,7 @@ export class SearchService extends Disposable implements ISearchService { return localResults; } - private matches(resource: uri, query: ISearchQuery): boolean { - // file pattern - if (query.filePattern) { - if (resource.scheme !== Schemas.file) { - return false; // if we match on file pattern, we have to ignore non file resources - } - - if (!strings.fuzzyContains(resource.fsPath, strings.stripWildcards(query.filePattern).toLowerCase())) { - return false; - } - } - + private matches(resource: uri, query: ITextQuery): boolean { // includes if (query.includePattern) { if (resource.scheme !== Schemas.file) { @@ -437,7 +402,6 @@ export class SearchService extends Disposable implements ISearchService { } export class DiskSearch implements ISearchResultProvider { - private raw: IRawSearchService; constructor(verboseLogging: boolean, timeout: number = 60 * 60 * 1000, searchDebug?: IDebugParams) { @@ -473,7 +437,22 @@ export class DiskSearch implements ISearchResultProvider { this.raw = new SearchChannelClient(channel); } - public search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise { + textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise { + const folderQueries = query.folderQueries || []; + return TPromise.join(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath))) + .then(exists => { + if (token && token.isCancellationRequested) { + throw canceled(); + } + + query.folderQueries = folderQueries.filter((q, index) => exists[index]); + const event: Event = this.raw.textSearch(query); + + return DiskSearch.collectResultsFromEvent(event, onProgress, token); + }); + } + + fileSearch(query: IFileQuery, token?: CancellationToken): TPromise { const folderQueries = query.folderQueries || []; return TPromise.join(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath))) .then(exists => { @@ -485,17 +464,13 @@ export class DiskSearch implements ISearchResultProvider { const rawSearch = this.rawSearchQuery(query, existingFolders); let event: Event; - if (query.type === QueryType.File) { - event = this.raw.fileSearch(rawSearch); - } else { - event = this.raw.textSearch(rawSearch); - } + event = this.raw.fileSearch(rawSearch); - return DiskSearch.collectResultsFromEvent(event, onProgress, token); + return DiskSearch.collectResultsFromEvent(event, null, token); }); } - private rawSearchQuery(query: ISearchQuery, existingFolders: IFolderQuery[]) { + private rawSearchQuery(query: IFileQuery, existingFolders: IFolderQuery[]) { let rawSearch: IRawSearch = { folderQueries: [], extraFiles: [], @@ -506,11 +481,7 @@ export class DiskSearch implements ISearchResultProvider { exists: query.exists, sortByScore: query.sortByScore, cacheKey: query.cacheKey, - useRipgrep: query.useRipgrep, - disregardIgnoreFiles: query.disregardIgnoreFiles, - disregardGlobalIgnoreFiles: query.disregardGlobalIgnoreFiles, - ignoreSymlinks: query.ignoreSymlinks, - previewOptions: query.previewOptions + useRipgrep: query.useRipgrep }; for (const q of existingFolders) { @@ -532,10 +503,6 @@ export class DiskSearch implements ISearchResultProvider { } } - if (query.type === QueryType.Text) { - rawSearch.contentPattern = query.contentPattern; - } - return rawSearch; } diff --git a/src/vs/workbench/services/search/node/textSearchAdapter.ts b/src/vs/workbench/services/search/node/textSearchAdapter.ts index 669a37bf1c8..1f33e0bcea7 100644 --- a/src/vs/workbench/services/search/node/textSearchAdapter.ts +++ b/src/vs/workbench/services/search/node/textSearchAdapter.ts @@ -4,22 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { URI } from 'vs/base/common/uri'; import * as extfs from 'vs/base/node/extfs'; -import { IFileMatch, IFolderQuery, IProgress, ISearchQuery, ITextSearchStats, QueryType } from 'vs/platform/search/common/search'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; +import { IFileMatch, IProgress, ITextQuery, ITextSearchStats } from 'vs/platform/search/common/search'; import { RipgrepTextSearchEngine } from 'vs/workbench/services/search/node/ripgrepTextSearchEngine'; import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import { ISerializedFileMatch, ISerializedSearchSuccess } from './search'; export class TextSearchEngineAdapter { - constructor(private config: IRawSearch) { + constructor(private query: ITextQuery) { } // TODO@Rob - make promise-based once the old search is gone, and I don't need them to have matching interfaces anymore search(token: CancellationToken, onResult: (matches: ISerializedFileMatch[]) => void, onMessage: (message: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void { - if (!this.config.folderQueries.length && !this.config.extraFiles.length) { + if (!this.query.folderQueries.length && !this.query.extraFileResources.length) { done(null, { type: 'success', limitHit: false, @@ -30,39 +28,12 @@ export class TextSearchEngineAdapter { return; } - const query: ISearchQuery = { - type: QueryType.Text, - cacheKey: this.config.cacheKey, - contentPattern: this.config.contentPattern, - - excludePattern: this.config.excludePattern, - includePattern: this.config.includePattern, - extraFileResources: this.config.extraFiles && this.config.extraFiles.map(f => URI.file(f)), - fileEncoding: this.config.folderQueries[0].fileEncoding, // ? - maxResults: this.config.maxResults, - exists: this.config.exists, - sortByScore: this.config.sortByScore, - disregardIgnoreFiles: this.config.disregardIgnoreFiles, - disregardGlobalIgnoreFiles: this.config.disregardGlobalIgnoreFiles, - ignoreSymlinks: this.config.ignoreSymlinks, - maxFileSize: this.config.maxFilesize, - previewOptions: this.config.previewOptions - }; - query.folderQueries = this.config.folderQueries.map(fq => { - disregardGlobalIgnoreFiles: fq.disregardGlobalIgnoreFiles, - disregardIgnoreFiles: fq.disregardIgnoreFiles, - excludePattern: fq.excludePattern, - fileEncoding: fq.fileEncoding, - folder: URI.file(fq.folder), - includePattern: fq.includePattern - }); - const pretendOutputChannel = { appendLine(msg) { onMessage({ message: msg }); } }; - const textSearchManager = new TextSearchManager(this.config.contentPattern, query, new RipgrepTextSearchEngine(pretendOutputChannel), extfs); + const textSearchManager = new TextSearchManager(this.query, new RipgrepTextSearchEngine(pretendOutputChannel), extfs); textSearchManager .search( matches => { diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 5b49e34d08b..20dc143411e 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -11,9 +11,9 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import * as extfs from 'vs/base/node/extfs'; -import { IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ISearchQuery, ITextSearchResult } from 'vs/platform/search/common/search'; -import * as vscode from 'vscode'; +import { IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchResult } from 'vs/platform/search/common/search'; import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search'; +import * as vscode from 'vscode'; export class TextSearchManager { @@ -22,11 +22,11 @@ export class TextSearchManager { private isLimitHit: boolean; private resultCount = 0; - constructor(private pattern: IPatternInfo, private config: ISearchQuery, private provider: vscode.TextSearchProvider, private _extfs: typeof extfs) { + constructor(private query: ITextQuery, private provider: vscode.TextSearchProvider, private _extfs: typeof extfs) { } public search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): TPromise { - const folderQueries = this.config.folderQueries; + const folderQueries = this.query.folderQueries; const tokenSource = new CancellationTokenSource(); token.onCancellationRequested(() => tokenSource.cancel()); @@ -39,7 +39,7 @@ export class TextSearchManager { return; } - if (this.resultCount >= this.config.maxResults) { + if (this.resultCount >= this.query.maxResults) { this.isLimitHit = true; isCanceled = true; tokenSource.cancel(); @@ -77,7 +77,7 @@ export class TextSearchManager { } private searchInFolder(folderQuery: IFolderQuery, onResult: (result: vscode.TextSearchResult) => void, token: CancellationToken): TPromise { - const queryTester = new QueryGlobTester(this.config, folderQuery); + const queryTester = new QueryGlobTester(this.query, folderQuery); const testingPs: TPromise[] = []; const progress = { report: (result: vscode.TextSearchResult) => { @@ -98,7 +98,7 @@ export class TextSearchManager { const searchOptions = this.getSearchOptionsForFolder(folderQuery); return new TPromise(resolve => process.nextTick(resolve)) - .then(() => this.provider.provideTextSearchResults(patternInfoToQuery(this.pattern), searchOptions, progress, token)) + .then(() => this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token)) .then(result => { return TPromise.join(testingPs) .then(() => result); @@ -118,20 +118,20 @@ export class TextSearchManager { } private getSearchOptionsForFolder(fq: IFolderQuery): vscode.TextSearchOptions { - const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern); - const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern); + const includes = resolvePatternsForProvider(this.query.includePattern, fq.includePattern); + const excludes = resolvePatternsForProvider(this.query.excludePattern, fq.excludePattern); return { folder: URI.from(fq.folder), excludes, includes, - useIgnoreFiles: !this.config.disregardIgnoreFiles, - useGlobalIgnoreFiles: !this.config.disregardGlobalIgnoreFiles, - followSymlinks: !this.config.ignoreSymlinks, - encoding: this.config.fileEncoding, - maxFileSize: this.config.maxFileSize, - maxResults: this.config.maxResults, - previewOptions: this.config.previewOptions + useIgnoreFiles: !fq.disregardIgnoreFiles, + useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, + followSymlinks: !fq.ignoreSymlinks, + encoding: this.query.fileEncoding, + maxFileSize: this.query.maxFileSize, + maxResults: this.query.maxResults, + previewOptions: this.query.previewOptions }; } } 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 07ed6478cf2..8a642b62ca3 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -3,19 +3,21 @@ * 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 glob from 'vs/base/common/glob'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { FileWalker } from 'vs/workbench/services/search/node/fileSearch'; -import { ISerializedFileMatch } from 'vs/workbench/services/search/node/search'; -import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/legacy/textSearch'; -import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; -import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/legacy/textSearchWorkerProvider'; +import * as path from 'path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IFolderSearch, IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; +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 { ISerializedFileMatch } from 'vs/workbench/services/search/node/search'; +import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; function countAll(matches: ISerializedFileMatch[]): number { return matches.reduce((acc, m) => acc + m.numMatches, 0); @@ -24,14 +26,14 @@ function countAll(matches: ISerializedFileMatch[]): number { 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 TEST_ROOT_FOLDER: IFolderQuery = { folder: URI.file(TEST_FIXTURES) }; +const ROOT_FOLDER_QUERY: IFolderQuery[] = [ TEST_ROOT_FOLDER ]; -const MULTIROOT_QUERIES: IFolderSearch[] = [ - { folder: EXAMPLES_FIXTURES }, - { folder: MORE_FIXTURES } +const MULTIROOT_QUERIES: IFolderQuery[] = [ + { folder: URI.file(EXAMPLES_FIXTURES) }, + { folder: URI.file(MORE_FIXTURES) } ]; const textSearchWorkerProvider = new TextSearchWorkerProvider(); @@ -62,9 +64,9 @@ function doLegacySearchTest(config: IRawSearch, expectedResultCount: number | Fu }); } -function doRipgrepSearchTest(config: IRawSearch, expectedResultCount: number | Function): TPromise { +function doRipgrepSearchTest(query: ITextQuery, expectedResultCount: number | Function): TPromise { return new TPromise((resolve, reject) => { - let engine = new TextSearchEngineAdapter(config); + let engine = new TextSearchEngineAdapter(query); let c = 0; engine.search(new CancellationTokenSource().token, (results) => { @@ -88,16 +90,18 @@ function doRipgrepSearchTest(config: IRawSearch, expectedResultCount: number | F }); } -function doSearchTest(config: IRawSearch, expectedResultCount: number) { - return doLegacySearchTest(config, expectedResultCount) - .then(() => doRipgrepSearchTest(config, expectedResultCount)); +function doSearchTest(query: ITextQuery, expectedResultCount: number) { + const legacyQuery = rawSearchQuery(query); + return doLegacySearchTest(legacyQuery, expectedResultCount) + .then(() => doRipgrepSearchTest(query, expectedResultCount)); } suite('Search-integration', function () { this.timeout(1000 * 60); // increase timeout for this suite test('Text: GameOfLife', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: 'GameOfLife' }, }; @@ -106,7 +110,8 @@ suite('Search-integration', function () { }); test('Text: GameOfLife (RegExp)', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: 'Game.?fL\\w?fe', isRegExp: true } }; @@ -115,7 +120,8 @@ suite('Search-integration', function () { }); test('Text: GameOfLife (RegExp to EOL)', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: 'GameOfLife.*', isRegExp: true } }; @@ -124,7 +130,8 @@ suite('Search-integration', function () { }); test('Text: GameOfLife (Word Match, Case Sensitive)', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: 'GameOfLife', isWordMatch: true, isCaseSensitive: true } }; @@ -133,7 +140,8 @@ suite('Search-integration', function () { }); test('Text: GameOfLife (Word Match, Spaces)', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: ' GameOfLife ', isWordMatch: true } }; @@ -142,7 +150,8 @@ suite('Search-integration', function () { }); test('Text: GameOfLife (Word Match, Punctuation and Spaces)', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: ', as =', isWordMatch: true } }; @@ -151,7 +160,8 @@ suite('Search-integration', function () { }); test('Text: Helvetica (UTF 16)', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: 'Helvetica' } }; @@ -160,7 +170,8 @@ suite('Search-integration', function () { }); test('Text: e', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: 'e' } }; @@ -233,7 +244,8 @@ suite('Search-integration', function () { test('Text: a (capped)', () => { const maxResults = 520; - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: 'a' }, maxResults @@ -241,12 +253,13 @@ 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(config, count => count < maxResults * 2) + return doLegacySearchTest(rawSearchQuery(config), count => count < maxResults * 2) .then(() => doRipgrepSearchTest(config, maxResults)); }); test('Text: a (no results)', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: 'ahsogehtdas' } }; @@ -255,7 +268,8 @@ suite('Search-integration', function () { }); test('Text: -size', () => { - const config = { + const config = { + type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, contentPattern: { pattern: '-size' } }; @@ -264,7 +278,8 @@ suite('Search-integration', function () { }); test('Multiroot: Conway', () => { - const config: IRawSearch = { + const config: ITextQuery = { + type: QueryType.Text, folderQueries: MULTIROOT_QUERIES, contentPattern: { pattern: 'conway' } }; @@ -273,7 +288,8 @@ suite('Search-integration', function () { }); test('Multiroot: e with partial global exclude', () => { - const config: IRawSearch = { + const config: ITextQuery = { + type: QueryType.Text, folderQueries: MULTIROOT_QUERIES, contentPattern: { pattern: 'e' }, excludePattern: makeExpression('**/*.txt') @@ -283,7 +299,8 @@ suite('Search-integration', function () { }); test('Multiroot: e with global excludes', () => { - const config: IRawSearch = { + const config: ITextQuery = { + type: QueryType.Text, folderQueries: MULTIROOT_QUERIES, contentPattern: { pattern: 'e' }, excludePattern: makeExpression('**/*.txt', '**/*.js') @@ -293,10 +310,11 @@ suite('Search-integration', function () { }); test('Multiroot: e with folder exclude', () => { - const config: IRawSearch = { + const config: ITextQuery = { + type: QueryType.Text, folderQueries: [ - { folder: EXAMPLES_FIXTURES, excludePattern: makeExpression('**/e*.js') }, - { folder: MORE_FIXTURES } + { folder: URI.file(EXAMPLES_FIXTURES), excludePattern: makeExpression('**/e*.js') }, + { folder: URI.file(MORE_FIXTURES) } ], contentPattern: { pattern: 'e' } }; diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index fc8f8e17a6a..f9451294556 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import * as extfs from 'vs/base/node/extfs'; -import { IFileMatch, IPatternInfo, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats, ISearchQuery, QueryType } from 'vs/platform/search/common/search'; +import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType } from 'vs/platform/search/common/search'; import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/node/extHost.protocol'; +import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { Range } from 'vs/workbench/api/node/extHostTypes'; import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; -import * as vscode from 'vscode'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { TestLogService } from 'vs/workbench/test/workbenchTestServices'; -import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; +import * as vscode from 'vscode'; let rpcProtocol: TestRPCProtocol; let extHostSearch: ExtHostSearch; @@ -78,7 +78,7 @@ suite('ExtHostSearch', () => { await rpcProtocol.sync(); } - async function runFileSearch(query: IRawSearchQuery, cancel = false): Promise<{ results: URI[]; stats: ISearchCompleteStats }> { + async function runFileSearch(query: IFileQuery, cancel = false): Promise<{ results: URI[]; stats: ISearchCompleteStats }> { let stats: ISearchCompleteStats; try { const cancellation = new CancellationTokenSource(); @@ -103,11 +103,11 @@ suite('ExtHostSearch', () => { }; } - async function runTextSearch(pattern: IPatternInfo, query: IRawSearchQuery, cancel = false): Promise<{ results: IFileMatch[], stats: ISearchCompleteStats }> { + async function runTextSearch(query: ITextQuery, cancel = false): Promise<{ results: IFileMatch[], stats: ISearchCompleteStats }> { let stats: ISearchCompleteStats; try { const cancellation = new CancellationTokenSource(); - const p = extHostSearch.$provideTextSearchResults(mockMainThreadSearch.lastHandle, 0, pattern, query, cancellation.token); + const p = extHostSearch.$provideTextSearchResults(mockMainThreadSearch.lastHandle, 0, query, cancellation.token); if (cancel) { await new TPromise(resolve => process.nextTick(resolve)); cancellation.cancel(); @@ -157,7 +157,7 @@ suite('ExtHostSearch', () => { suite('File:', () => { - function getSimpleQuery(filePattern = ''): ISearchQuery { + function getSimpleQuery(filePattern = ''): IFileQuery { return { type: QueryType.File, @@ -639,9 +639,10 @@ suite('ExtHostSearch', () => { }; } - function getSimpleQuery(): ISearchQuery { + function getSimpleQuery(queryText: string): ITextQuery { return { type: QueryType.Text, + contentPattern: getPattern(queryText), folderQueries: [ { folder: rootFolderA } @@ -699,7 +700,7 @@ suite('ExtHostSearch', () => { } }); - const { results, stats } = await runTextSearch(getPattern('foo'), getSimpleQuery()); + const { results, stats } = await runTextSearch(getSimpleQuery('foo')); assert(!stats.limitHit); assert(!results.length); }); @@ -717,7 +718,7 @@ suite('ExtHostSearch', () => { } }); - const { results, stats } = await runTextSearch(getPattern('foo'), getSimpleQuery()); + const { results, stats } = await runTextSearch(getSimpleQuery('foo')); assert(!stats.limitHit); assertResults(results, providedResults); }); @@ -731,8 +732,9 @@ suite('ExtHostSearch', () => { } }); - const query: IRawSearchQuery = { + const query: ITextQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), includePattern: { '*.ts': true @@ -748,7 +750,7 @@ suite('ExtHostSearch', () => { ] }; - await runTextSearch(getPattern('foo'), query); + await runTextSearch(query); }); test('global/local include/excludes combined', async () => { @@ -766,8 +768,9 @@ suite('ExtHostSearch', () => { } }); - const query: IRawSearchQuery = { + const query: ITextQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), includePattern: { '*.ts': true @@ -789,7 +792,7 @@ suite('ExtHostSearch', () => { ] }; - await runTextSearch(getPattern('foo'), query); + await runTextSearch(query); }); test('include/excludes resolved correctly', async () => { @@ -804,6 +807,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), includePattern: { '*.ts': true, @@ -826,7 +830,7 @@ suite('ExtHostSearch', () => { ] }; - await runTextSearch(getPattern('foo'), query); + await runTextSearch(query); }); test('provider fail', async () => { @@ -837,7 +841,7 @@ suite('ExtHostSearch', () => { }); try { - await runTextSearch(getPattern('foo'), getSimpleQuery()); + await runTextSearch(getSimpleQuery('foo')); assert(false, 'Expected to fail'); } catch { // expected to fail @@ -870,6 +874,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), excludePattern: { '*.js': { @@ -882,7 +887,7 @@ suite('ExtHostSearch', () => { ] }; - const { results } = await runTextSearch(getPattern('foo'), query); + const { results } = await runTextSearch(query); assertResults(results, providedResults.slice(1)); }); @@ -929,6 +934,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), excludePattern: { '*.js': { @@ -954,7 +960,7 @@ suite('ExtHostSearch', () => { ] }; - const { results } = await runTextSearch(getPattern('foo'), query); + const { results } = await runTextSearch(query); assertResults(results, [ makeTextResult(rootFolderA, 'folder/fileA.scss'), makeTextResult(rootFolderA, 'folder/file2.css'), @@ -978,6 +984,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), includePattern: { '*.ts': true @@ -988,7 +995,7 @@ suite('ExtHostSearch', () => { ] }; - const { results } = await runTextSearch(getPattern('foo'), query); + const { results } = await runTextSearch(query); assertResults(results, providedResults.slice(1)); }); @@ -1009,6 +1016,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), maxResults: 1, @@ -1017,7 +1025,7 @@ suite('ExtHostSearch', () => { ] }; - const { results, stats } = await runTextSearch(getPattern('foo'), query); + const { results, stats } = await runTextSearch(query); assert(stats.limitHit, 'Expected to return limitHit'); assertResults(results, providedResults.slice(0, 1)); assert(wasCanceled, 'Expected to be canceled'); @@ -1041,6 +1049,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), maxResults: 2, @@ -1049,7 +1058,7 @@ suite('ExtHostSearch', () => { ] }; - const { results, stats } = await runTextSearch(getPattern('foo'), query); + const { results, stats } = await runTextSearch(query); assert(stats.limitHit, 'Expected to return limitHit'); assertResults(results, providedResults.slice(0, 2)); assert(wasCanceled, 'Expected to be canceled'); @@ -1072,6 +1081,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), maxResults: 2, @@ -1080,7 +1090,7 @@ suite('ExtHostSearch', () => { ] }; - const { results, stats } = await runTextSearch(getPattern('foo'), query); + const { results, stats } = await runTextSearch(query); assert(!stats.limitHit, 'Expected not to return limitHit'); assertResults(results, providedResults); assert(!wasCanceled, 'Expected not to be canceled'); @@ -1102,6 +1112,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), maxResults: 1000, @@ -1110,7 +1121,7 @@ suite('ExtHostSearch', () => { ] }; - const { results, stats } = await runTextSearch(getPattern('foo'), query); + const { results, stats } = await runTextSearch(query); assert(stats.limitHit, 'Expected to return limitHit'); assertResults(results, providedResults); }); @@ -1132,6 +1143,7 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), maxResults: 2, @@ -1141,7 +1153,7 @@ suite('ExtHostSearch', () => { ] }; - const { results } = await runTextSearch(getPattern('foo'), query); + const { results } = await runTextSearch(query); assert.equal(results.length, 2); assert.equal(cancels, 2); }); @@ -1162,13 +1174,14 @@ suite('ExtHostSearch', () => { const query: ISearchQuery = { type: QueryType.Text, + contentPattern: getPattern('foo'), folderQueries: [ { folder: fancySchemeFolderA } ] }; - const { results } = await runTextSearch(getPattern('foo'), query); + const { results } = await runTextSearch(query); assertResults(results, providedResults); }); }); diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index b763fdf321e..ce851d814f6 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -9,7 +9,7 @@ import * as fs from 'fs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; -import { ISearchService, IQueryOptions } from 'vs/platform/search/common/search'; +import { ISearchService } from 'vs/platform/search/common/search'; import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -28,7 +28,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { SearchModel } from 'vs/workbench/parts/search/common/searchModel'; -import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; +import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; import * as event from 'vs/base/common/event'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; @@ -73,7 +73,7 @@ suite.skip('TextSearch performance (integration)', () => { [ILogService, new NullLogService()] )); - const queryOptions: IQueryOptions = { + const queryOptions: ITextQueryBuilderOptions = { maxResults: 2048 };