Skip to content

Commit

Permalink
Fix #55790 - allow extensions to return a hitLimit flag
Browse files Browse the repository at this point in the history
  • Loading branch information
roblourens committed Sep 18, 2018
1 parent c7cff38 commit f3654c5
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 66 deletions.
8 changes: 5 additions & 3 deletions extensions/search-rg/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class RipgrepSearchProvider implements vscode.FileIndexProvider, vscode.TextSear
process.once('exit', () => this.dispose());
}

provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Thenable<void> {
provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Thenable<vscode.TextSearchComplete> {
const engine = new RipgrepTextSearchEngine(this.outputChannel);
return this.withEngine(engine, () => engine.provideTextSearchResults(query, options, progress, token));
}
Expand All @@ -43,10 +43,12 @@ class RipgrepSearchProvider implements vscode.FileIndexProvider, vscode.TextSear
.then(() => results);
}

private withEngine(engine: SearchEngine, fn: () => Thenable<void>): Thenable<void> {
private withEngine<T>(engine: SearchEngine, fn: () => Thenable<T>): Thenable<T> {
this.inProgress.add(engine);
return fn().then(() => {
return fn().then(result => {
this.inProgress.delete(engine);

return result;
});
}

Expand Down
25 changes: 6 additions & 19 deletions extensions/search-rg/src/ripgrepTextSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ import { anchorGlob, createTextSearchResult } from './utils';
// 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');

// TODO@roblou move to SearchService
const MAX_TEXT_RESULTS = 10000;

export class RipgrepTextSearchEngine {
private isDone = false;
private rgProc: cp.ChildProcess;
Expand All @@ -39,7 +36,7 @@ export class RipgrepTextSearchEngine {
}
}

provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Thenable<void> {
provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Thenable<vscode.TextSearchComplete> {
this.outputChannel.appendLine(`provideTextSearchResults ${query.pattern}, ${JSON.stringify({
...options,
...{
Expand Down Expand Up @@ -67,13 +64,15 @@ export class RipgrepTextSearchEngine {
});

let gotResult = false;
this.ripgrepParser = new RipgrepParser(MAX_TEXT_RESULTS, cwd, options.previewOptions);
this.ripgrepParser = new RipgrepParser(options.maxResults, cwd, options.previewOptions);
this.ripgrepParser.on('result', (match: vscode.TextSearchResult) => {
gotResult = true;
progress.report(match);
});

let limitHit = false;
this.ripgrepParser.on('hitLimit', () => {
limitHit = true;
this.cancel();
});

Expand All @@ -96,7 +95,7 @@ export class RipgrepTextSearchEngine {
this.outputChannel.appendLine(gotResult ? 'Got result from parser' : 'No result from parser');
this.outputChannel.appendLine('');
if (this.isDone) {
resolve();
resolve({ limitHit });
} else {
// Trigger last result
this.ripgrepParser.flush();
Expand All @@ -105,7 +104,7 @@ export class RipgrepTextSearchEngine {
if (stderr && !gotData && (displayMsg = rgErrorMsgForDisplay(stderr))) {
reject(new Error(displayMsg));
} else {
resolve();
resolve({ limitHit });
}
}
});
Expand Down Expand Up @@ -219,16 +218,6 @@ export class RipgrepParser extends EventEmitter {
lineText = stripUTF8BOM(lineText);
}

// if (!this.currentFile) {
// // When searching a single file and no folderQueries, rg does not print the file line, so create it here
// const singleFile = this.extraSearchFiles[0];
// if (!singleFile) {
// throw new Error('Got match line for unknown file');
// }

// this.currentFile = this.getFileUri(singleFile);
// }

let lastMatchEndPos = 0;
let matchTextStartPos = -1;

Expand Down Expand Up @@ -373,8 +362,6 @@ function getRgArgs(query: vscode.TextSearchQuery, options: vscode.TextSearchOpti
return args;
}

// TODO@roblou organize away

interface RegExpOptions {
matchCase?: boolean;
wholeWord?: boolean;
Expand Down
20 changes: 17 additions & 3 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ declare module 'vscode' {
encoding?: string;
}

/**
* Information collected when text search is complete.
*/
export interface TextSearchComplete {
/**
* Whether the search hit the limit on the maximum number of search results.
* `maxResults` on [`TextSearchOptions`](#TextSearchOptions) specifies the max number of results.
* - If exactly that number of matches exist, this should be false.
* - If `maxResults` matches are returned and more exist, this should be true.
* - If search hits an internal limit which is less than `maxResults`, this should be true.
*/
limitHit?: boolean;
}

/**
* The parameters of a query for file search.
*/
Expand Down Expand Up @@ -258,7 +272,7 @@ declare module 'vscode' {
* @param progress A progress callback that must be invoked for all results.
* @param token A cancellation token.
*/
provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress<TextSearchResult>, token: CancellationToken): Thenable<void>;
provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress<TextSearchResult>, token: CancellationToken): Thenable<TextSearchComplete>;
}

/**
Expand Down Expand Up @@ -354,7 +368,7 @@ declare module 'vscode' {
* @param token A token that can be used to signal cancellation to the underlying search engine.
* @return A thenable that resolves when the search is complete.
*/
export function findTextInFiles(query: TextSearchQuery, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable<void>;
export function findTextInFiles(query: TextSearchQuery, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable<TextSearchComplete>;

/**
* Search text in files across all [workspace folders](#workspace.workspaceFolders) in the workspace.
Expand All @@ -364,7 +378,7 @@ declare module 'vscode' {
* @param token A token that can be used to signal cancellation to the underlying search engine.
* @return A thenable that resolves when the search is complete.
*/
export function findTextInFiles(query: TextSearchQuery, options: FindTextInFilesOptions, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable<void>;
export function findTextInFiles(query: TextSearchQuery, options: FindTextInFilesOptions, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable<TextSearchComplete>;
}

//#endregion
Expand Down
7 changes: 4 additions & 3 deletions src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { TextSearchComplete } from 'vscode';

@extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
Expand Down Expand Up @@ -168,7 +169,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
});
}

$startTextSearch(pattern: IPatternInfo, options: IQueryOptions, requestId: number, token: CancellationToken): Thenable<void> {
$startTextSearch(pattern: IPatternInfo, options: IQueryOptions, requestId: number, token: CancellationToken): Thenable<TextSearchComplete> {
const workspace = this._contextService.getWorkspace();
const folders = workspace.folders.map(folder => folder.uri);

Expand All @@ -182,8 +183,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
};

const search = this._searchService.search(query, token, onProgress).then(
() => {
return null;
result => {
return { limitHit: result.limitHit };
},
err => {
if (!isPromiseCanceledError(err)) {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/node/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ export interface ExtHostUrlsShape {

export interface MainThreadWorkspaceShape extends IDisposable {
$startFileSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Thenable<UriComponents[]>;
$startTextSearch(query: IPatternInfo, options: IQueryOptions, requestId: number, token: CancellationToken): Thenable<void>;
$startTextSearch(query: IPatternInfo, options: IQueryOptions, requestId: number, token: CancellationToken): Thenable<vscode.TextSearchComplete>;
$checkExists(includes: string[], token: CancellationToken): Thenable<boolean>;
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void>;
Expand Down
63 changes: 29 additions & 34 deletions src/vs/workbench/api/node/extHostSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,13 @@ class TextSearchEngine {
// For each root folder
TPromise.join(folderQueries.map((fq, i) => {
return this.searchInFolder(fq, r => onResult(r, i), tokenSource.token);
})).then(() => {
})).then(results => {
tokenSource.dispose();
this.collector.flush();

const someFolderHitLImit = results.some(result => result && result.limitHit);
resolve({
limitHit: this.isLimitHit,
limitHit: this.isLimitHit || someFolderHitLImit,
stats: {
type: 'textSearchProvider'
}
Expand All @@ -335,40 +337,33 @@ class TextSearchEngine {
});
}

private searchInFolder(folderQuery: IFolderQuery<URI>, onResult: (result: vscode.TextSearchResult) => void, token: CancellationToken): TPromise<void> {
return new TPromise((resolve, reject) => {

const queryTester = new QueryGlobTester(this.config, folderQuery);
const testingPs = [];
const progress = {
report: (result: vscode.TextSearchResult) => {
const hasSibling = folderQuery.folder.scheme === 'file' && glob.hasSiblingPromiseFn(() => {
return this.readdir(path.dirname(result.uri.fsPath));
});
private searchInFolder(folderQuery: IFolderQuery<URI>, onResult: (result: vscode.TextSearchResult) => void, token: CancellationToken): TPromise<vscode.TextSearchComplete> {
const queryTester = new QueryGlobTester(this.config, folderQuery);
const testingPs = [];
const progress = {
report: (result: vscode.TextSearchResult) => {
const hasSibling = folderQuery.folder.scheme === 'file' && glob.hasSiblingPromiseFn(() => {
return this.readdir(path.dirname(result.uri.fsPath));
});

const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath);
testingPs.push(
queryTester.includedInQuery(relativePath, path.basename(relativePath), hasSibling)
.then(included => {
if (included) {
onResult(result);
}
}));
}
};
const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath);
testingPs.push(
queryTester.includedInQuery(relativePath, path.basename(relativePath), hasSibling)
.then(included => {
if (included) {
onResult(result);
}
}));
}
};

const searchOptions = this.getSearchOptionsForFolder(folderQuery);
new TPromise(resolve => process.nextTick(resolve))
.then(() => {
return this.provider.provideTextSearchResults(patternInfoToQuery(this.pattern), searchOptions, progress, token);
})
.then(() => {
return TPromise.join(testingPs);
})
.then(
() => resolve(null),
reject);
});
const searchOptions = this.getSearchOptionsForFolder(folderQuery);
return new TPromise(resolve => process.nextTick(resolve))
.then(() => this.provider.provideTextSearchResults(patternInfoToQuery(this.pattern), searchOptions, progress, token))
.then(result => {
return TPromise.join(testingPs)
.then(() => result);
});
}

private readdir(dirname: string): TPromise<string[]> {
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/api/node/extHostWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
.then(data => Array.isArray(data) ? data.map(URI.revive) : []);
}

findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: string, token: vscode.CancellationToken = CancellationToken.None) {
findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: string, token: vscode.CancellationToken = CancellationToken.None): Thenable<vscode.TextSearchComplete> {
this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId}, entryPoint: findTextInFiles`);

if (options.previewOptions && options.previewOptions.totalChars <= options.previewOptions.leadingChars) {
Expand Down Expand Up @@ -432,8 +432,9 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
}

return this._proxy.$startTextSearch(query, queryOptions, requestId, token).then(
() => {
result => {
delete this._activeSearchCallbacks[requestId];
return result;
},
err => {
delete this._activeSearchCallbacks[requestId];
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/parts/search/browser/searchView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const $ = dom.$;

export class SearchView extends Viewlet implements IViewlet, IPanel {

private static readonly MAX_TEXT_RESULTS = 10000;
private static readonly MAX_TEXT_RESULTS = 1000;
private static readonly SHOW_REPLACE_STORAGE_KEY = 'vs.search.show.replace';

private static readonly WIDE_CLASS_NAME = 'wide';
Expand Down

0 comments on commit f3654c5

Please sign in to comment.