diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 68ebc39b1d2b64ca4eaa7e8a656acfcbe1236a59..806f170635871c21c984329982dd375ceae8f23e 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -183,7 +183,7 @@ export function resultIsMatch(result: ITextSearchResult): result is ITextSearchM } export interface IProgressMessage { - message?: string; + message: string; } export type ISearchProgressItem = IFileMatch | IProgressMessage; @@ -192,8 +192,8 @@ export function isFileMatch(p: ISearchProgressItem): p is IFileMatch { return !!(p).resource; } -export function isProgressMessage(p: ISearchProgressItem): p is IProgressMessage { - return !isFileMatch(p); +export function isProgressMessage(p: ISearchProgressItem | ISerializedSearchProgressItem): p is IProgressMessage { + return !!(p as IProgressMessage).message; } export interface ISearchCompleteStats { @@ -468,7 +468,7 @@ export interface IRawFileMatch { * * If not given, the search algorithm should use `relativePath`. */ - searchPath?: string; + searchPath: string | undefined; } export interface ISearchEngine { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 97fe8231b8678bda1775fd80c53c0501d369c85e..719ad873b504622a8fbacc2b78e923ae9ec55968 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -24,9 +24,8 @@ import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFil import { spawnRipgrepCmd } from './ripgrepFileSearch'; import { prepareQuery } from 'vs/base/common/fuzzyScorer'; -interface IDirectoryEntry { +interface IDirectoryEntry extends IRawFileMatch { base: string; - relativePath: string; basename: string; } @@ -122,7 +121,7 @@ export class FileWalker { } // File: Check for match on file pattern and include pattern - this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */ }); + this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */, searchPath: undefined }); }); this.cmdSW = StopWatch.create(false); @@ -260,7 +259,7 @@ export class FileWalker { } // TODO: Optimize siblings clauses with ripgrep here. - this.addDirectoryEntries(tree, rootFolder, relativeFiles, onResult); + this.addDirectoryEntries(folderQuery, tree, rootFolder, relativeFiles, onResult); if (last) { this.matchDirectoryTree(tree, rootFolder, onResult); @@ -389,13 +388,17 @@ export class FileWalker { return tree; } - private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: string, relativeFiles: string[], onResult: (result: IRawFileMatch) => void) { + private addDirectoryEntries(folderQuery: IFolderQuery, { pathToEntries }: IDirectoryTree, base: string, relativeFiles: string[], onResult: (result: IRawFileMatch) => void) { // Support relative paths to files from a root resource (ignores excludes) if (relativeFiles.indexOf(this.filePattern) !== -1) { - this.matchFile(onResult, { base: base, relativePath: this.filePattern }); + this.matchFile(onResult, { + base, + relativePath: this.filePattern, + searchPath: this.getSearchPath(folderQuery, this.filePattern) + }); } - function add(relativePath: string) { + const add = (relativePath: string) => { const basename = path.basename(relativePath); const dirname = path.dirname(relativePath); let entries = pathToEntries[dirname]; @@ -406,9 +409,10 @@ export class FileWalker { entries.push({ base, relativePath, - basename + basename, + searchPath: this.getSearchPath(folderQuery, relativePath), }); - } + }; relativeFiles.forEach(add); } diff --git a/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts new file mode 100644 index 0000000000000000000000000000000000000000..427de8a4fd9808bf2f22f03b237ea4f5f393150d --- /dev/null +++ b/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; +import * as path from 'vs/base/common/path'; +import { URI } from 'vs/base/common/uri'; +import { IFileQuery, IFolderQuery, ISerializedSearchProgressItem, isProgressMessage, QueryType } from 'vs/workbench/services/search/common/search'; +import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; + +const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); +const TEST_FIXTURES2 = path.normalize(getPathFromAmdModule(require, './fixtures2')); +const EXAMPLES_FIXTURES = path.join(TEST_FIXTURES, 'examples'); +const MORE_FIXTURES = path.join(TEST_FIXTURES, 'more'); +const TEST_ROOT_FOLDER: IFolderQuery = { folder: URI.file(TEST_FIXTURES) }; +const ROOT_FOLDER_QUERY: IFolderQuery[] = [ + TEST_ROOT_FOLDER +]; + +const MULTIROOT_QUERIES: IFolderQuery[] = [ + { folder: URI.file(EXAMPLES_FIXTURES), folderName: 'examples_folder' }, + { folder: URI.file(MORE_FIXTURES) } +]; + +async function doSearchTest(query: IFileQuery, expectedResultCount: number | Function): Promise { + const svc = new SearchService(); + + const results: ISerializedSearchProgressItem[] = []; + await svc.doFileSearch(query, e => { + if (!isProgressMessage(e)) { + if (Array.isArray(e)) { + results.push(...e); + } else { + results.push(e); + } + } + }); + + assert.equal(results.length, expectedResultCount, `rg ${results.length} !== ${expectedResultCount}`); +} + +suite('FileSearch-integration', function () { + this.timeout(1000 * 60); // increase timeout for this suite + + test('File - simple', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: ROOT_FOLDER_QUERY + }; + + return doSearchTest(config, 14); + }); + + test('File - filepattern', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: ROOT_FOLDER_QUERY, + filePattern: 'anotherfile' + }; + + return doSearchTest(config, 1); + }); + + test('File - exclude', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: ROOT_FOLDER_QUERY, + filePattern: 'file', + excludePattern: { '**/anotherfolder/**': true } + }; + + return doSearchTest(config, 2); + }); + + test('File - multiroot', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: MULTIROOT_QUERIES, + filePattern: 'file', + excludePattern: { '**/anotherfolder/**': true } + }; + + return doSearchTest(config, 2); + }); + + test('File - multiroot with folder name', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: MULTIROOT_QUERIES, + filePattern: 'examples_folder anotherfile' + }; + + return doSearchTest(config, 1); + }); + + test('File - multiroot with folder name and sibling exclude', () => { + const config: IFileQuery = { + type: QueryType.File, + folderQueries: [ + { folder: URI.file(TEST_FIXTURES), folderName: 'folder1' }, + { folder: URI.file(TEST_FIXTURES2) } + ], + filePattern: 'folder1 site', + excludePattern: { '*.css': { when: '$(basename).less' } } + }; + + return doSearchTest(config, 1); + }); +}); diff --git a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts index b61f702fc56e3dff8f0512522e64c7e3c1685915..1626179ccab0a8c553c5fb1b59227e0128a0b6c9 100644 --- a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts @@ -83,6 +83,7 @@ suite('RawSearchService', () => { const rawMatch: IRawFileMatch = { base: path.normalize('/some'), relativePath: 'where', + searchPath: undefined }; const match: ISerializedFileMatch = { @@ -232,7 +233,8 @@ suite('RawSearchService', () => { base: path.normalize('/some/where'), relativePath, basename: relativePath, - size: 3 + size: 3, + searchPath: undefined })); const Engine = TestSearchEngine.bind(null, () => matches.shift()!); const service = new RawSearchService(); @@ -291,7 +293,8 @@ suite('RawSearchService', () => { base: path.normalize('/some/where'), relativePath, basename: relativePath, - size: 3 + size: 3, + searchPath: undefined })); const Engine = TestSearchEngine.bind(null, () => matches.shift()!); const service = new RawSearchService(); @@ -340,6 +343,7 @@ suite('RawSearchService', () => { matches.push({ base: path.normalize('/some/where'), relativePath: 'bc', + searchPath: undefined }); const results: any[] = []; const cb: IProgressCallback = value => { 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 6e8d77a82a5ba0bbd941a8f4fa44c2af72e1a07f..1d2ad24fb75276960aae42870465e67f29432d7e 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -46,7 +46,7 @@ function doSearchTest(query: ITextQuery, expectedResultCount: number | Function) }); } -suite('Search-integration', function () { +suite('TextSearch-integration', function () { this.timeout(1000 * 60); // increase timeout for this suite test('Text: GameOfLife', () => {