diff --git a/extensions/search-rg/src/cachedSearchProvider.ts b/extensions/search-rg/src/cachedSearchProvider.ts index 78932b5deb48c..a17e6de102f96 100644 --- a/extensions/search-rg/src/cachedSearchProvider.ts +++ b/extensions/search-rg/src/cachedSearchProvider.ts @@ -8,14 +8,19 @@ import * as vscode from 'vscode'; import * as arrays from './common/arrays'; import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from './common/fileSearchScorer'; import * as strings from './common/strings'; +import { joinPath } from './ripgrepHelpers'; interface IProviderArgs { query: vscode.FileSearchQuery; options: vscode.FileSearchOptions; - progress: vscode.Progress; + progress: vscode.Progress; token: vscode.CancellationToken; } +export interface IInternalFileSearchProvider { + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable; +} + export class CachedSearchProvider { private static readonly BATCH_SIZE = 512; @@ -25,12 +30,12 @@ export class CachedSearchProvider { constructor(private outputChannel: vscode.OutputChannel) { } - provideFileSearchResults(provider: vscode.SearchProvider, query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(provider: IInternalFileSearchProvider, query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { const onResult = (result: IInternalFileMatch) => { - progress.report(result.relativePath); + progress.report(joinPath(options.folder, result.relativePath)); }; - const providerArgs = { + const providerArgs: IProviderArgs = { query, options, progress, token }; @@ -52,23 +57,14 @@ export class CachedSearchProvider { }); } - private doSortedSearch(args: IProviderArgs, provider: vscode.SearchProvider): Promise { - let searchPromise: Promise; + private doSortedSearch(args: IProviderArgs, provider: IInternalFileSearchProvider): Promise { let allResultsPromise = new Promise((c, e) => { - let results: IInternalFileMatch[] = []; - - const onResult = (progress: OneOrMore) => { - if (Array.isArray(progress)) { - results.push(...progress); - } else { - results.push(progress); - } - }; + const results: IInternalFileMatch[] = []; + const onResult = (progress: IInternalFileMatch[]) => results.push(...progress); - searchPromise = this.doSearch(args, provider, onResult, CachedSearchProvider.BATCH_SIZE) - .then(() => { - c(results); - }, e); + // set maxResult = null + this.doSearch(args, provider, onResult, CachedSearchProvider.BATCH_SIZE) + .then(() => c(results), e); }); let cache: Cache; @@ -80,12 +76,9 @@ export class CachedSearchProvider { }); } - return new Promise((c, e) => { - allResultsPromise.then(results => { - const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); - return this.sortResults(args, results, scorerCache) - .then(c); - }, e); + return allResultsPromise.then(results => { + const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); + return this.sortResults(args, results, scorerCache); }); } @@ -119,7 +112,7 @@ export class CachedSearchProvider { const preparedQuery = prepareQuery(args.query.pattern); const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, preparedQuery, true, FileMatchItemAccessor, scorerCache); - return arrays.topAsync(results, compare, args.options.maxResults || 10000, 10000); + return arrays.topAsync(results, compare, args.options.maxResults || 0, 10000); } private getResultsFromCache(cache: Cache, searchValue: string, onResult: (results: IInternalFileMatch) => void): Promise<[IInternalFileMatch[], CacheStats]> { @@ -177,7 +170,7 @@ export class CachedSearchProvider { }); } - private doSearch(args: IProviderArgs, provider: vscode.SearchProvider, onResult: (result: OneOrMore) => void, batchSize?: number): Promise { + private doSearch(args: IProviderArgs, provider: IInternalFileSearchProvider, onResult: (result: IInternalFileMatch[]) => void, batchSize: number): Promise { return new Promise((c, e) => { let batch: IInternalFileMatch[] = []; const onProviderResult = (match: string) => { @@ -187,19 +180,15 @@ export class CachedSearchProvider { basename: path.basename(match) }; - if (batchSize) { - batch.push(internalMatch); - if (batchSize > 0 && batch.length >= batchSize) { - onResult(batch); - batch = []; - } - } else { - onResult(internalMatch); + batch.push(internalMatch); + if (batchSize > 0 && batch.length >= batchSize) { + onResult(batch); + batch = []; } } }; - provider.provideFileSearchResults(args.query, args.options, { report: onProviderResult }, args.token).then(() => { + provider.provideFileSearchResults(args.options, { report: onProviderResult }, args.token).then(() => { if (batch.length) { onResult(batch); } @@ -221,13 +210,6 @@ export class CachedSearchProvider { } } -function joinPath(resource: vscode.Uri, pathFragment: string): vscode.Uri { - const joinedPath = path.join(resource.path || '/', pathFragment); - return resource.with({ - path: joinedPath - }); -} - interface IInternalFileMatch { relativePath?: string; // Not present for extraFiles or absolute path matches basename: string; @@ -246,8 +228,6 @@ interface CacheEntry { onResult?: Event; } -type OneOrMore = T | T[]; - class Cache { public resultsToSearchCache: { [searchValue: string]: CacheEntry } = Object.create(null); public scorerCache: ScorerCache = Object.create(null); diff --git a/extensions/search-rg/src/extension.ts b/extensions/search-rg/src/extension.ts index 079ae5f12158d..042d2269a8052 100644 --- a/extensions/search-rg/src/extension.ts +++ b/extensions/search-rg/src/extension.ts @@ -28,7 +28,7 @@ class RipgrepSearchProvider implements vscode.SearchProvider { return engine.provideTextSearchResults(query, options, progress, token); } - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.SearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { const engine = new RipgrepFileSearch(this.outputChannel); return this.cachedProvider.provideFileSearchResults(engine, query, options, progress, token); } diff --git a/extensions/search-rg/src/ripgrepFileSearch.ts b/extensions/search-rg/src/ripgrepFileSearch.ts index dd0efaa0ed6e8..f6765d970bf4f 100644 --- a/extensions/search-rg/src/ripgrepFileSearch.ts +++ b/extensions/search-rg/src/ripgrepFileSearch.ts @@ -11,13 +11,14 @@ import { normalizeNFC, normalizeNFD } from './common/normalization'; import { rgPath } from './ripgrep'; import { anchorGlob } from './ripgrepHelpers'; import { rgErrorMsgForDisplay } from './ripgrepTextSearch'; +import { IInternalFileSearchProvider } from './cachedSearchProvider'; const isMac = process.platform === 'darwin'; // If vscode-ripgrep is in an .asar file, then the binary is unpacked. const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); -export class RipgrepFileSearch { +export class RipgrepFileSearch implements IInternalFileSearchProvider { private rgProc: cp.ChildProcess; private killRgProcFn: (code?: number) => void; @@ -30,7 +31,7 @@ export class RipgrepFileSearch { process.removeListener('exit', this.killRgProcFn); } - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { this.outputChannel.appendLine(`provideFileSearchResults ${JSON.stringify({ ...options, ...{ diff --git a/extensions/search-rg/src/ripgrepHelpers.ts b/extensions/search-rg/src/ripgrepHelpers.ts index 5487be750a03e..fd083fe075f2b 100644 --- a/extensions/search-rg/src/ripgrepHelpers.ts +++ b/extensions/search-rg/src/ripgrepHelpers.ts @@ -5,9 +5,8 @@ 'use strict'; -import * as vscode from 'vscode'; - import * as path from 'path'; +import * as vscode from 'vscode'; export function fixDriveC(_path: string): string { const root = path.parse(_path).root; @@ -19,3 +18,10 @@ export function fixDriveC(_path: string): string { export function anchorGlob(glob: string): string { return glob.startsWith('**') || glob.startsWith('/') ? glob : `/${glob}`; } + +export function joinPath(resource: vscode.Uri, pathFragment: string): vscode.Uri { + const joinedPath = path.join(resource.path || '/', pathFragment); + return resource.with({ + path: joinedPath + }); +} diff --git a/extensions/search-rg/src/ripgrepTextSearch.ts b/extensions/search-rg/src/ripgrepTextSearch.ts index b03ee60c7549a..87aed94c6e316 100644 --- a/extensions/search-rg/src/ripgrepTextSearch.ts +++ b/extensions/search-rg/src/ripgrepTextSearch.ts @@ -7,6 +7,7 @@ import * as cp from 'child_process'; import { EventEmitter } from 'events'; +import * as path from 'path'; import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import * as vscode from 'vscode'; import { rgPath } from './ripgrep'; @@ -291,7 +292,7 @@ export class RipgrepParser extends EventEmitter { lineMatches .map(range => { return { - path: this.currentFile, + uri: vscode.Uri.file(path.join(this.rootFolder, this.currentFile)), range, preview: { text: preview, diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e299a3abf1caa..3cc94a1cefed9 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -45,7 +45,7 @@ declare module 'vscode' { export interface FileSearchOptions extends SearchOptions { } export interface TextSearchResult { - path: string; + uri: Uri; range: Range; // For now, preview must be a single line of text @@ -53,7 +53,7 @@ declare module 'vscode' { } export interface SearchProvider { - provideFileSearchResults?(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; + provideFileSearchResults?(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; provideTextSearchResults?(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 980fbcefb9ae2..923fb9b16df34 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -483,7 +483,7 @@ export interface MainThreadFileSystemShape extends IDisposable { export interface MainThreadSearchShape extends IDisposable { $registerSearchProvider(handle: number, scheme: string): void; $unregisterProvider(handle: number): void; - $handleFileMatch(handle: number, session: number, data: UriComponents | UriComponents[]): void; + $handleFileMatch(handle: number, session: number, data: UriComponents[]): void; $handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void; $handleTelemetry(eventName: string, data: any): void; } diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 57f5e8362ca31..aeba2749c5f27 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -8,13 +8,13 @@ import * as path from 'path'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as glob from 'vs/base/common/glob'; -import { joinPath } from 'vs/base/common/resources'; +import * as resources from 'vs/base/common/resources'; import * as strings from 'vs/base/common/strings'; import URI, { UriComponents } from 'vs/base/common/uri'; import { PPromise, TPromise } from 'vs/base/common/winjs.base'; import * as extfs from 'vs/base/node/extfs'; import * as pfs from 'vs/base/node/pfs'; -import { IFileMatch, IFolderQuery, IPatternInfo, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search'; +import { IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search'; import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol'; @@ -120,29 +120,28 @@ function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolder } class TextSearchResultsCollector { - private _batchedCollector: BatchedCollector; + private _batchedCollector: BatchedCollector; private _currentFolderIdx: number; - private _currentRelativePath: string; - private _currentFileMatch: IRawFileMatch2; + private _currentUri: URI; + private _currentFileMatch: IFileMatch; - constructor(private folderQueries: IFolderQuery[], private _onResult: (result: IRawFileMatch2[]) => void) { - this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); + constructor(private _onResult: (result: IFileMatch[]) => void) { + this._batchedCollector = new BatchedCollector(512, items => this.sendItems(items)); } add(data: vscode.TextSearchResult, folderIdx: number): void { // Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector. // This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search // providers that send results in random order. We could do this step afterwards instead. - if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || this._currentRelativePath !== data.path)) { + if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || resources.isEqual(this._currentUri, data.uri))) { this.pushToCollector(); this._currentFileMatch = null; } if (!this._currentFileMatch) { - const resource = joinPath(this.folderQueries[folderIdx].folder, data.path); this._currentFileMatch = { - resource, + resource: data.uri, lineMatches: [] }; } @@ -168,8 +167,8 @@ class TextSearchResultsCollector { this._batchedCollector.flush(); } - private sendItems(items: IRawFileMatch2 | IRawFileMatch2[]): void { - this._onResult(Array.isArray(items) ? items : [items]); + private sendItems(items: IFileMatch[]): void { + this._onResult(items); } } @@ -190,7 +189,7 @@ class BatchedCollector { private batchSize = 0; private timeoutHandle: number; - constructor(private maxBatchSize: number, private cb: (items: T | T[]) => void) { + constructor(private maxBatchSize: number, private cb: (items: T[]) => void) { } addItem(item: T, size: number): void { @@ -198,11 +197,7 @@ class BatchedCollector { return; } - if (this.maxBatchSize > 0) { - this.addItemToBatch(item, size); - } else { - this.cb(item); - } + this.addItemToBatch(item, size); } addItems(items: T[], size: number): void { @@ -378,11 +373,11 @@ class TextSearchEngine { this.activeCancellationTokens = new Set(); } - public search(): PPromise<{ limitHit: boolean }, IRawFileMatch2[]> { + public search(): PPromise<{ limitHit: boolean }, IFileMatch[]> { const folderQueries = this.config.folderQueries; - return new PPromise<{ limitHit: boolean }, IRawFileMatch2[]>((resolve, reject, _onResult) => { - this.collector = new TextSearchResultsCollector(this.config.folderQueries, _onResult); + return new PPromise<{ limitHit: boolean }, IFileMatch[]>((resolve, reject, _onResult) => { + this.collector = new TextSearchResultsCollector(_onResult); const onResult = (match: vscode.TextSearchResult, folderIdx: number) => { if (this.isCanceled) { @@ -425,11 +420,12 @@ class TextSearchEngine { const progress = { report: (result: vscode.TextSearchResult) => { const siblingFn = folderQuery.folder.scheme === 'file' && (() => { - return this.readdir(path.dirname(path.join(folderQuery.folder.fsPath, result.path))); + return this.readdir(path.dirname(result.uri.fsPath)); }); + const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath); testingPs.push( - queryTester.includedInQuery(result.path, path.basename(result.path), siblingFn) + queryTester.includedInQuery(relativePath, path.basename(relativePath), siblingFn) .then(included => { if (included) { onResult(result); @@ -598,23 +594,20 @@ class FileSearchEngine { let cancellation = new CancellationTokenSource(); return new PPromise((resolve, reject, onResult) => { const options = this.getSearchOptionsForFolder(fq); - let filePatternSeen = false; const tree = this.initDirectoryTree(); const queryTester = new QueryGlobTester(this.config, fq); const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses(); - const onProviderResult = (relativePath: string) => { + const onProviderResult = (result: URI) => { if (this.isCanceled) { return; } - if (noSiblingsClauses) { - if (relativePath === this.filePattern) { - filePatternSeen = true; - } + const relativePath = path.relative(fq.folder.fsPath, result.fsPath); - const basename = path.basename(relativePath); + if (noSiblingsClauses) { + const basename = path.basename(result.fsPath); this.matchFile(onResult, { base: fq.folder, relativePath, basename }); return; @@ -640,18 +633,16 @@ class FileSearchEngine { } if (noSiblingsClauses && this.isLimitHit) { - if (!filePatternSeen) { - // If the limit was hit, check whether filePattern is an exact relative match because it must be included - return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => { - if (exists) { - onResult({ - base: fq.folder, - relativePath: this.filePattern, - basename: path.basename(this.filePattern), - }); - } - }); - } + // If the limit was hit, check whether filePattern is an exact relative match because it must be included + return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => { + if (exists) { + onResult({ + base: fq.folder, + relativePath: this.filePattern, + basename: path.basename(this.filePattern), + }); + } + }); } this.matchDirectoryTree(tree, queryTester, onResult); @@ -841,7 +832,7 @@ class FileSearchManager { private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch { return { - resource: joinPath(match.base, match.relativePath) + resource: resources.joinPath(match.base, match.relativePath) }; } diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 89dcbf5660512..4ab4525526d3b 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -399,7 +399,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { lineMatch.offsetAndLengths.forEach(offsetAndLength => { const range = new Range(lineMatch.lineNumber, offsetAndLength[0], lineMatch.lineNumber, offsetAndLength[0] + offsetAndLength[1]); callback({ - path: URI.revive(p.resource).fsPath, + uri: URI.revive(p.resource), preview: { text: lineMatch.preview, match: range }, range }); 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 5ace3f232cd3d..3c5cceb4ce718 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -5,7 +5,6 @@ 'use strict'; import * as assert from 'assert'; -import * as path from 'path'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; @@ -36,12 +35,12 @@ class MockMainThreadSearch implements MainThreadSearchShape { $unregisterProvider(handle: number): void { } - $handleFindMatch(handle: number, session: number, data: UriComponents | IRawFileMatch2[]): void { - if (Array.isArray(data)) { - this.results.push(...data); - } else { - this.results.push(data); - } + $handleFileMatch(handle: number, session: number, data: UriComponents[]): void { + this.results.push(...data); + } + + $handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void { + this.results.push(...data); } $handleTelemetry(eventName: string, data: any): void { @@ -155,7 +154,7 @@ suite('ExtHostSearch', () => { test('no results', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return TPromise.wrap(null); } }); @@ -173,8 +172,8 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); return TPromise.wrap(null); } }); @@ -188,11 +187,11 @@ suite('ExtHostSearch', () => { test('Search canceled', async () => { let cancelRequested = false; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return new TPromise((resolve, reject) => { token.onCancellationRequested(() => { cancelRequested = true; - progress.report('file1.ts'); + progress.report(joinPath(options.folder, 'file1.ts')); resolve(null); // or reject or nothing? }); @@ -213,8 +212,11 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(r)); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults + .map(relativePath => joinPath(options.folder, relativePath)) + .forEach(r => progress.report(r)); + throw new Error('I broke'); } }); @@ -229,7 +231,7 @@ suite('ExtHostSearch', () => { test('provider returns null', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { return null; } }); @@ -244,7 +246,7 @@ suite('ExtHostSearch', () => { test('all provider calls get global include/excludes', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, 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); } @@ -273,7 +275,7 @@ suite('ExtHostSearch', () => { test('global/local include/excludes combined', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, 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']); @@ -315,7 +317,7 @@ suite('ExtHostSearch', () => { test('include/excludes resolved correctly', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { assert.deepEqual(options.includes.sort(), ['*.jsx', '*.ts']); assert.deepEqual(options.excludes.sort(), []); @@ -358,8 +360,10 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(r)); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults + .map(relativePath => joinPath(options.folder, relativePath)) + .forEach(r => progress.report(r)); return TPromise.wrap(null); } }); @@ -389,20 +393,20 @@ suite('ExtHostSearch', () => { test('multiroot sibling exclude clause', async () => { await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - let reportedResults; + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + let reportedResults: URI[]; if (options.folder.fsPath === rootFolderA.fsPath) { reportedResults = [ 'folder/fileA.scss', 'folder/fileA.css', 'folder/file2.css' - ]; + ].map(relativePath => joinPath(rootFolderA, relativePath)); } else { reportedResults = [ 'fileB.ts', 'fileB.js', 'file3.js' - ]; + ].map(relativePath => joinPath(rootFolderB, relativePath)); } reportedResults.forEach(r => progress.report(r)); @@ -460,8 +464,9 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults + .forEach(r => progress.report(r)); token.onCancellationRequested(() => wasCanceled = true); return TPromise.wrap(null); @@ -497,8 +502,8 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); token.onCancellationRequested(() => wasCanceled = true); return TPromise.wrap(null); @@ -533,8 +538,8 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); token.onCancellationRequested(() => wasCanceled = true); return TPromise.wrap(null); @@ -564,7 +569,7 @@ suite('ExtHostSearch', () => { test('multiroot max results', async () => { let cancels = 0; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { token.onCancellationRequested(() => cancels++); // Provice results async so it has a chance to invoke every provider @@ -574,9 +579,8 @@ suite('ExtHostSearch', () => { 'file1.ts', 'file2.ts', 'file3.ts', - ].forEach(f => { - progress.report(f); - }); + ].map(relativePath => joinPath(options.folder, relativePath)) + .forEach(r => progress.report(r)); }); } }); @@ -610,8 +614,8 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); return TPromise.wrap(null); } }); @@ -642,8 +646,8 @@ suite('ExtHostSearch', () => { ]; await registerTestSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(path.basename(r.fsPath))); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + reportedResults.forEach(r => progress.report(r)); return TPromise.wrap(null); } }, fancyScheme); @@ -672,7 +676,7 @@ suite('ExtHostSearch', () => { // ]; // await registerTestSearchProvider({ - // provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + // provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { // reportedResults.forEach(r => progress.report(r)); // return TPromise.wrap(null); // } @@ -694,11 +698,11 @@ suite('ExtHostSearch', () => { }; } - function makeTextResult(relativePath: string): vscode.TextSearchResult { + function makeTextResult(baseFolder: URI, relativePath: string): vscode.TextSearchResult { return { preview: makePreview('foo'), range: new Range(0, 0, 0, 3), - path: relativePath + uri: joinPath(baseFolder, relativePath) }; } @@ -718,17 +722,16 @@ suite('ExtHostSearch', () => { }; } - function assertResults(actual: IFileMatch[], expected: vscode.TextSearchResult[], folder = rootFolderA) { + function assertResults(actual: IFileMatch[], expected: vscode.TextSearchResult[]) { const actualTextSearchResults: vscode.TextSearchResult[] = []; for (let fileMatch of actual) { // Make relative - const relativePath = fileMatch.resource.toString().substr(folder.toString().length + 1); for (let lineMatch of fileMatch.lineMatches) { for (let [offset, length] of lineMatch.offsetAndLengths) { actualTextSearchResults.push({ preview: { text: lineMatch.preview, match: null }, range: new Range(lineMatch.lineNumber, offset, lineMatch.lineNumber, length + offset), - path: relativePath + uri: fileMatch.resource }); } } @@ -741,7 +744,7 @@ suite('ExtHostSearch', () => { .map(r => ({ ...r, ...{ - uri: r.path.toString(), + uri: r.uri.toString(), range: rangeToString(r.range), preview: { text: r.preview.text, @@ -769,8 +772,8 @@ suite('ExtHostSearch', () => { test('basic results', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts') + makeTextResult(rootFolderA, 'file1.ts'), + makeTextResult(rootFolderA, 'file2.ts') ]; await registerTestSearchProvider({ @@ -920,8 +923,8 @@ suite('ExtHostSearch', () => { }; const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.js'), - makeTextResult('file1.ts') + makeTextResult(rootFolderA, 'file1.js'), + makeTextResult(rootFolderA, 'file1.ts') ]; await registerTestSearchProvider({ @@ -973,15 +976,15 @@ suite('ExtHostSearch', () => { let reportedResults; if (options.folder.fsPath === rootFolderA.fsPath) { reportedResults = [ - makeTextResult('folder/fileA.scss'), - makeTextResult('folder/fileA.css'), - makeTextResult('folder/file2.css') + makeTextResult(rootFolderA, 'folder/fileA.scss'), + makeTextResult(rootFolderA, 'folder/fileA.css'), + makeTextResult(rootFolderA, 'folder/file2.css') ]; } else { reportedResults = [ - makeTextResult('fileB.ts'), - makeTextResult('fileB.js'), - makeTextResult('file3.js') + makeTextResult(rootFolderB, 'fileB.ts'), + makeTextResult(rootFolderB, 'fileB.js'), + makeTextResult(rootFolderB, 'file3.js') ]; } @@ -1019,17 +1022,17 @@ suite('ExtHostSearch', () => { const { results } = await runTextSearch(getPattern('foo'), query); assertResults(results, [ - makeTextResult('folder/fileA.scss'), - makeTextResult('folder/file2.css'), - makeTextResult('fileB.ts'), - makeTextResult('fileB.js'), - makeTextResult('file3.js')]); + makeTextResult(rootFolderA, 'folder/fileA.scss'), + makeTextResult(rootFolderA, 'folder/file2.css'), + makeTextResult(rootFolderB, 'fileB.ts'), + makeTextResult(rootFolderB, 'fileB.js'), + makeTextResult(rootFolderB, 'file3.js')]); }); test('include pattern applied', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.js'), - makeTextResult('file1.ts') + makeTextResult(rootFolderA, 'file1.js'), + makeTextResult(rootFolderA, 'file1.ts') ]; await registerTestSearchProvider({ @@ -1057,8 +1060,8 @@ suite('ExtHostSearch', () => { test('max results = 1', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts') + makeTextResult(rootFolderA, 'file1.ts'), + makeTextResult(rootFolderA, 'file2.ts') ]; let wasCanceled = false; @@ -1088,9 +1091,9 @@ suite('ExtHostSearch', () => { test('max results = 2', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts'), - makeTextResult('file3.ts') + makeTextResult(rootFolderA, 'file1.ts'), + makeTextResult(rootFolderA, 'file2.ts'), + makeTextResult(rootFolderA, 'file3.ts') ]; let wasCanceled = false; @@ -1120,8 +1123,8 @@ suite('ExtHostSearch', () => { test('provider returns maxResults exactly', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts') + makeTextResult(rootFolderA, 'file1.ts'), + makeTextResult(rootFolderA, 'file2.ts') ]; let wasCanceled = false; @@ -1160,7 +1163,7 @@ suite('ExtHostSearch', () => { 'file1.ts', 'file2.ts', 'file3.ts', - ].forEach(f => progress.report(makeTextResult(f))); + ].forEach(f => progress.report(makeTextResult(options.folder, f))); }); } }); @@ -1183,9 +1186,9 @@ suite('ExtHostSearch', () => { test('works with non-file schemes', async () => { const providedResults: vscode.TextSearchResult[] = [ - makeTextResult('file1.ts'), - makeTextResult('file2.ts'), - makeTextResult('file3.ts') + makeTextResult(fancySchemeFolderA, 'file1.ts'), + makeTextResult(fancySchemeFolderA, 'file2.ts'), + makeTextResult(fancySchemeFolderA, 'file3.ts') ]; await registerTestSearchProvider({ @@ -1204,7 +1207,7 @@ suite('ExtHostSearch', () => { }; const { results } = await runTextSearch(getPattern('foo'), query); - assertResults(results, providedResults, fancySchemeFolderA); + assertResults(results, providedResults); }); }); });