diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index bb2a8df08e4..bdd1918a876 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -44,6 +44,12 @@ export interface ICommonQueryOptions { filePattern?: string; // file search only fileEncoding?: string; maxResults?: number; + /** + * 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; diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index cda0a718027..a28fb2b6a9b 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -232,13 +232,13 @@ export class ExtensionHostMain { const query: ISearchQuery = { folderQueries, type: QueryType.File, - maxResults: 1, + exists: true, includePattern: includes, useRipgrep }; let result = await this._diskSearch.search(query); - if (result.results.length > 0) { + if (result.limitHit) { // a file was found matching one of the glob patterns return ( this._extensionService.activateById(extensionId, true) diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index ff457516718..02e7c5e570c 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -53,6 +53,7 @@ export class FileWalker { private normalizedFilePatternLowercase: string; private includePattern: glob.ParsedExpression; private maxResults: number; + private exists: boolean; private maxFilesize: number; private isLimitHit: boolean; private resultCount: number; @@ -77,6 +78,7 @@ export class FileWalker { this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || null; + this.exists = config.exists; this.maxFilesize = config.maxFilesize || null; this.walkedPaths = Object.create(null); this.resultCount = 0; @@ -234,6 +236,7 @@ export class FileWalker { return; } if (this.isLimitHit) { + done(); return; } @@ -392,9 +395,12 @@ export class FileWalker { cmd.on('close', (code: number) => { // ripgrep returns code=1 when no results are found - if (code !== 0 && ((isRipgrep && stderr.length) || !isRipgrep)) { + if (code !== 0 && (!isRipgrep || code !== 1)) { done(new Error(`command failed with error code ${code}: ${this.decodeData(stderr, encoding)}`)); } else { + if (isRipgrep && this.exists && code === 0) { + this.isLimitHit = true; + } done(null, '', true); } }); @@ -657,7 +663,7 @@ export class FileWalker { if (this.isFilePatternMatch(candidate.relativePath) && (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename))) { this.resultCount++; - if (this.maxResults && this.resultCount > this.maxResults) { + if (this.exists || (this.maxResults && this.resultCount > this.maxResults)) { this.isLimitHit = true; } diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index 7b4b508b4e4..caba0ebd233 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -44,6 +44,10 @@ function getRgArgs(config: IRawSearch, folderQuery: IFolderSearch, includePatter // Follow symlinks args.push('--follow'); + if (config.exists) { + args.push('--quiet'); + } + // Folder to search args.push('--'); diff --git a/src/vs/workbench/services/search/node/search.ts b/src/vs/workbench/services/search/node/search.ts index 424b3b4f0f7..38c3be3415c 100644 --- a/src/vs/workbench/services/search/node/search.ts +++ b/src/vs/workbench/services/search/node/search.ts @@ -25,6 +25,7 @@ export interface IRawSearch { includePattern?: IExpression; contentPattern?: IPatternInfo; maxResults?: number; + exists?: boolean; sortByScore?: boolean; cacheKey?: string; maxFilesize?: number; diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index b7adc8e63a9..85c20bd1f10 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -266,6 +266,7 @@ export class DiskSearch implements ISearchResultProvider { excludePattern: query.excludePattern, includePattern: query.includePattern, maxResults: query.maxResults, + exists: query.exists, sortByScore: query.sortByScore, cacheKey: query.cacheKey, useRipgrep: query.useRipgrep, diff --git a/src/vs/workbench/services/search/test/node/search.test.ts b/src/vs/workbench/services/search/test/node/search.test.ts index abecd589774..f7fbebd6835 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -84,6 +84,88 @@ suite('FileSearchEngine', () => { }); }); + test('Files: exists', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: ROOT_FOLDER_QUERY, + includePattern: { '**/file.txt': true }, + exists: true + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(complete.limitHit); + done(); + }); + }); + + test('Files: not exists', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: ROOT_FOLDER_QUERY, + includePattern: { '**/nofile.txt': true }, + exists: true + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(!complete.limitHit); + done(); + }); + }); + + test('Files: exists without Ripgrep', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: ROOT_FOLDER_QUERY, + includePattern: { '**/file.txt': true }, + exists: true, + useRipgrep: false + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(complete.limitHit); + done(); + }); + }); + + test('Files: not exists without Ripgrep', function (done: () => void) { + let engine = new FileSearchEngine({ + folderQueries: ROOT_FOLDER_QUERY, + includePattern: { '**/nofile.txt': true }, + exists: true, + useRipgrep: false + }); + + let count = 0; + engine.search((result) => { + if (result) { + count++; + } + }, () => { }, (error, complete) => { + assert.ok(!error); + assert.equal(count, 0); + assert.ok(!complete.limitHit); + done(); + }); + }); + test('Files: examples/com*', function (done: () => void) { let engine = new FileSearchEngine({ folderQueries: ROOT_FOLDER_QUERY,