diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index e16c1c1f2b8..2dedab400a5 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -83,15 +83,8 @@ export class ExtHostSearch implements ExtHostSearchShape { return TPromise.as(undefined); } - const includes: string[] = query.includePattern ? Object.keys(query.includePattern) : []; - if (folderQuery.includePattern) { - includes.push(...Object.keys(folderQuery.includePattern)); - } - - const excludes: string[] = query.excludePattern ? Object.keys(query.excludePattern) : []; - if (folderQuery.excludePattern) { - excludes.push(...Object.keys(folderQuery.excludePattern)); - } + const includes = resolvePatternsForProvider(query.includePattern, folderQuery.includePattern); + const excludes = resolvePatternsForProvider(query.excludePattern, folderQuery.excludePattern); const searchOptions: vscode.TextSearchOptions = { folder: URI.from(folderQuery.folder), @@ -113,6 +106,23 @@ export class ExtHostSearch implements ExtHostSearchShape { } } +/** + * TODO@roblou + * Discards sibling clauses (for now) and 'false' patterns + */ +function resolvePatternsForProvider(globalPattern: glob.IExpression, folderPattern: glob.IExpression): string[] { + const merged = { + ...(globalPattern || {}), + ...(folderPattern || {}) + }; + + return Object.keys(merged) + .filter(key => { + const value = merged[key]; + return typeof value === 'boolean' && value; + }); +} + function reviveQuery(rawQuery: IRawSearchQuery): ISearchQuery { return { ...rawQuery, @@ -402,10 +412,6 @@ class FileSearchEngine { .map(err => toErrorMessage(err)) .filter(msg => !!msg)[0]; - if (errMsg) { - console.error(errMsg); - } - reject(new Error(errMsg)); }); }); @@ -493,15 +499,8 @@ class FileSearchEngine { } private getSearchOptionsForFolder(fq: IFolderQuery): vscode.FileSearchOptions { - const includes: string[] = this.config.includePattern ? Object.keys(this.config.includePattern) : []; - if (fq.includePattern) { - includes.push(...Object.keys(fq.includePattern)); - } - - const excludes: string[] = this.config.excludePattern ? Object.keys(this.config.excludePattern) : []; - if (fq.excludePattern) { - excludes.push(...Object.keys(fq.excludePattern)); - } + const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern); + const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern); return { folder: fq.folder, 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 302d8b8e694..b3b03b1d0be 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -13,8 +13,8 @@ import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/node/extHos import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; import * as vscode from 'vscode'; -import { dispose } from '../../../../base/common/lifecycle'; -import { isPromiseCanceledError } from '../../../../base/common/errors'; +import { dispose } from 'vs/base/common/lifecycle'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; let rpcProtocol: TestRPCProtocol; let extHostSearch: ExtHostSearch; @@ -45,7 +45,7 @@ class MockMainThreadSearch implements MainThreadSearchShape { } } -suite.only('ExtHostSearch', () => { +suite('ExtHostSearch', () => { async function registerTestSearchProvider(provider: vscode.SearchProvider): TPromise { disposables.push(extHostSearch.registerSearchProvider('file', provider)); await rpcProtocol.sync(); @@ -85,16 +85,21 @@ suite.only('ExtHostSearch', () => { return rpcProtocol.sync(); }); - suite('File', () => { + suite('File:', () => { const rootFolderA = URI.parse('/foo/bar'); - const simpleQuery: ISearchQuery = { - type: QueryType.File, + const rootFolderB = URI.parse('/foo/bar2'); + // const rootFolderC = URI.parse('/foo/bar3'); - filePattern: '', - folderQueries: [ - { folder: rootFolderA } - ] - }; + function getSimpleQuery(filePattern = ''): ISearchQuery { + return { + type: QueryType.File, + + filePattern, + folderQueries: [ + { folder: rootFolderA } + ] + }; + } function makeFileResult(root: URI, relativePath: string): URI { return URI.parse( @@ -114,9 +119,8 @@ suite.only('ExtHostSearch', () => { } }); - return runFileSearch(simpleQuery).then(results => { - assert(!results.length); - }); + const results = await runFileSearch(getSimpleQuery()); + assert(!results.length); }); test('simple results', async () => { @@ -133,10 +137,9 @@ suite.only('ExtHostSearch', () => { } }); - return runFileSearch(simpleQuery).then(results => { - assert.equal(results.length, 3); - compareURIs(results, reportedResults); - }); + const results = await runFileSearch(getSimpleQuery()); + assert.equal(results.length, 3); + compareURIs(results, reportedResults); }); // Sibling clauses @@ -161,10 +164,177 @@ suite.only('ExtHostSearch', () => { } }); - return runFileSearch(simpleQuery, true).then(results => { - assert(cancelRequested); - assert(!results.length); - }); + const results = await runFileSearch(getSimpleQuery(), true); + assert(cancelRequested); + assert(!results.length); }); + + test('provider fail', async () => { + const reportedResults = [ + makeFileResult(rootFolderA, 'file1.ts'), + makeFileResult(rootFolderA, 'file2.ts'), + makeFileResult(rootFolderA, 'file3.ts'), + ]; + + await registerTestSearchProvider({ + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); + throw new Error('I broke'); + } + }); + + try { + await runFileSearch(getSimpleQuery()); + assert(false, 'Expected to fail'); + } catch { + // Expected to throw + } + }); + + test('provider returns null', async () => { + await registerTestSearchProvider({ + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + return null; + } + }); + + try { + await runFileSearch(getSimpleQuery()); + assert(false, 'Expected to fail'); + } catch { + // Expected to throw + } + }); + + test('all provider calls get global include/excludes', async () => { + await registerTestSearchProvider({ + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + assert(options.excludes.length === 2 && options.includes.length === 2, 'Missing global include/excludes'); + return TPromise.wrap(null); + } + }); + + const query: ISearchQuery = { + type: QueryType.File, + + filePattern: '', + includePattern: { + 'foo': true, + 'bar': true + }, + excludePattern: { + 'something': true, + 'else': true + }, + folderQueries: [ + { folder: rootFolderA }, + { folder: rootFolderB } + ] + }; + + await runFileSearch(query); + }); + + test('global/local include/excludes combined', async () => { + await registerTestSearchProvider({ + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + if (options.folder.toString() === rootFolderA.toString()) { + assert.deepEqual(options.includes.sort(), ['*.ts', 'foo']); + assert.deepEqual(options.excludes.sort(), ['*.js', 'bar']); + } else { + assert.deepEqual(options.includes.sort(), ['*.ts']); + assert.deepEqual(options.excludes.sort(), ['*.js']); + } + + return TPromise.wrap(null); + } + }); + + const query: ISearchQuery = { + type: QueryType.File, + + filePattern: '', + includePattern: { + '*.ts': true + }, + excludePattern: { + '*.js': true + }, + folderQueries: [ + { + folder: rootFolderA, + includePattern: { + 'foo': true + }, + excludePattern: { + 'bar': true + } + }, + { folder: rootFolderB } + ] + }; + + await runFileSearch(query); + }); + + test('include/excludes resolved correctly', async () => { + await registerTestSearchProvider({ + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + assert.deepEqual(options.includes.sort(), ['*.jsx', '*.ts']); + assert.deepEqual(options.excludes.sort(), []); + + return TPromise.wrap(null); + } + }); + + const query: ISearchQuery = { + type: QueryType.File, + + filePattern: '', + includePattern: { + '*.ts': true, + '*.jsx': false + }, + excludePattern: { + '*.js': true, + '*.tsx': false + }, + folderQueries: [ + { + folder: rootFolderA, + includePattern: { + '*.jsx': true + }, + excludePattern: { + '*.js': false + } + } + ] + }; + + await runFileSearch(query); + }); + + // Mock fs? + // test('Returns result for absolute path', async () => { + // const queriedFile = makeFileResult(rootFolderA, 'file2.ts'); + // const reportedResults = [ + // makeFileResult(rootFolderA, 'file1.ts'), + // queriedFile, + // makeFileResult(rootFolderA, 'file3.ts'), + // ]; + + // await registerTestSearchProvider({ + // provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + // reportedResults.forEach(r => progress.report(r)); + // return TPromise.wrap(null); + // } + // }); + + // const queriedFilePath = queriedFile.fsPath; + // const results = await runFileSearch(getSimpleQuery(queriedFilePath)); + // assert.equal(results.length, 1); + // compareURIs(results, [queriedFile]); + // }); }); });