提交 e41acb7f 编写于 作者: B Benjamin Pasero

Merge branch 'master' into ben/editor

...@@ -4,14 +4,13 @@ ...@@ -4,14 +4,13 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process'; import * as cp from 'child_process';
import * as path from 'path';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import { NodeStringDecoder, StringDecoder } from 'string_decoder';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { rgPath } from 'vscode-ripgrep'; import { rgPath } from 'vscode-ripgrep';
import { normalizeNFC, normalizeNFD } from './normalization'; import { normalizeNFC, normalizeNFD } from './normalization';
import { rgErrorMsgForDisplay } from './ripgrepTextSearch';
import { anchorGlob } from './ripgrepHelpers'; import { anchorGlob } from './ripgrepHelpers';
import { rgErrorMsgForDisplay } from './ripgrepTextSearch';
const isMac = process.platform === 'darwin'; const isMac = process.platform === 'darwin';
...@@ -19,12 +18,16 @@ const isMac = process.platform === 'darwin'; ...@@ -19,12 +18,16 @@ const isMac = process.platform === 'darwin';
const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked'); const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked');
export class RipgrepFileSearchEngine { export class RipgrepFileSearchEngine {
private isDone = false;
private rgProc: cp.ChildProcess; private rgProc: cp.ChildProcess;
private killRgProcFn: (code?: number) => void; private killRgProcFn: (code?: number) => void;
constructor(private outputChannel: vscode.OutputChannel) { constructor(private outputChannel: vscode.OutputChannel) {
this.killRgProcFn = () => this.rgProc && this.rgProc.kill(); this.killRgProcFn = () => this.rgProc && this.rgProc.kill();
process.once('exit', this.killRgProcFn);
}
private dispose() {
process.removeListener('exit', this.killRgProcFn);
} }
provideFileSearchResults(options: vscode.SearchOptions, progress: vscode.Progress<string>, token: vscode.CancellationToken): Thenable<void> { provideFileSearchResults(options: vscode.SearchOptions, progress: vscode.Progress<string>, token: vscode.CancellationToken): Thenable<void> {
...@@ -38,7 +41,7 @@ export class RipgrepFileSearchEngine { ...@@ -38,7 +41,7 @@ export class RipgrepFileSearchEngine {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let isDone = false; let isDone = false;
const cancel = () => { const cancel = () => {
this.isDone = true; isDone = true;
this.rgProc.kill(); this.rgProc.kill();
}; };
token.onCancellationRequested(cancel); token.onCancellationRequested(cancel);
...@@ -53,7 +56,7 @@ export class RipgrepFileSearchEngine { ...@@ -53,7 +56,7 @@ export class RipgrepFileSearchEngine {
this.outputChannel.appendLine(`rg ${escapedArgs}\n - cwd: ${cwd}\n`); this.outputChannel.appendLine(`rg ${escapedArgs}\n - cwd: ${cwd}\n`);
this.rgProc = cp.spawn(rgDiskPath, rgArgs, { cwd }); this.rgProc = cp.spawn(rgDiskPath, rgArgs, { cwd });
process.once('exit', this.killRgProcFn);
this.rgProc.on('error', e => { this.rgProc.on('error', e => {
console.log(e); console.log(e);
reject(e); reject(e);
...@@ -90,7 +93,6 @@ export class RipgrepFileSearchEngine { ...@@ -90,7 +93,6 @@ export class RipgrepFileSearchEngine {
}); });
if (last) { if (last) {
process.removeListener('exit', this.killRgProcFn);
if (isDone) { if (isDone) {
resolve(); resolve();
} else { } else {
...@@ -104,7 +106,12 @@ export class RipgrepFileSearchEngine { ...@@ -104,7 +106,12 @@ export class RipgrepFileSearchEngine {
} }
} }
}); });
}); }).then(
() => this.dispose(),
err => {
this.dispose();
return Promise.reject(err);
});
} }
private collectStdout(cmd: cp.ChildProcess, cb: (err: Error, stdout?: string, last?: boolean) => void): void { private collectStdout(cmd: cp.ChildProcess, cb: (err: Error, stdout?: string, last?: boolean) => void): void {
......
...@@ -5,17 +5,15 @@ ...@@ -5,17 +5,15 @@
'use strict'; 'use strict';
import * as vscode from 'vscode';
import { EventEmitter } from 'events';
import * as path from 'path';
import { StringDecoder, NodeStringDecoder } from 'string_decoder';
import * as cp from 'child_process'; import * as cp from 'child_process';
import { EventEmitter } from 'events';
import { NodeStringDecoder, StringDecoder } from 'string_decoder';
import * as vscode from 'vscode';
import { rgPath } from 'vscode-ripgrep'; import { rgPath } from 'vscode-ripgrep';
import { start } from 'repl';
import { anchorGlob } from './ripgrepHelpers'; import { anchorGlob } from './ripgrepHelpers';
// If vscode-ripgrep is in an .asar file, then the binary is unpacked. // 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'); const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked');
......
...@@ -401,13 +401,15 @@ class TextSearchEngine { ...@@ -401,13 +401,15 @@ class TextSearchEngine {
return; return;
} }
this.resultCount++;
this.collector.add(match, folderIdx);
if (this.resultCount >= this.config.maxResults) { if (this.resultCount >= this.config.maxResults) {
this.isLimitHit = true; this.isLimitHit = true;
this.cancel(); this.cancel();
} }
if (!this.isLimitHit) {
this.resultCount++;
this.collector.add(match, folderIdx);
}
}; };
// For each root folder // For each root folder
...@@ -492,7 +494,8 @@ class TextSearchEngine { ...@@ -492,7 +494,8 @@ class TextSearchEngine {
includes, includes,
useIgnoreFiles: !this.config.disregardIgnoreFiles, useIgnoreFiles: !this.config.disregardIgnoreFiles,
followSymlinks: !this.config.ignoreSymlinks, followSymlinks: !this.config.ignoreSymlinks,
encoding: this.config.fileEncoding encoding: this.config.fileEncoding,
maxFileSize: this.config.maxFileSize
}; };
} }
} }
...@@ -589,7 +592,7 @@ class FileSearchEngine { ...@@ -589,7 +592,7 @@ class FileSearchEngine {
PPromise.join(folderQueries.map(fq => { PPromise.join(folderQueries.map(fq => {
return this.searchInFolder(fq).then(null, null, onResult); return this.searchInFolder(fq).then(null, null, onResult);
})).then(() => { })).then(() => {
resolve({ isLimitHit: false }); resolve({ isLimitHit: this.isLimitHit });
}, (errs: Error[]) => { }, (errs: Error[]) => {
const errMsg = errs const errMsg = errs
.map(err => toErrorMessage(err)) .map(err => toErrorMessage(err))
......
...@@ -80,7 +80,7 @@ export class SettingsEditor2 extends BaseEditor { ...@@ -80,7 +80,7 @@ export class SettingsEditor2 extends BaseEditor {
this.remoteSearchThrottle = new ThrottledDelayer(200); this.remoteSearchThrottle = new ThrottledDelayer(200);
this.viewState = { settingsTarget: ConfigurationTarget.USER }; this.viewState = { settingsTarget: ConfigurationTarget.USER };
this._register(configurationService.onDidChangeConfiguration(() => this.settingsTree.refresh())); this._register(configurationService.onDidChangeConfiguration(() => this.refreshTreeAndMaintainFocus()));
} }
createEditor(parent: HTMLElement): void { createEditor(parent: HTMLElement): void {
...@@ -193,6 +193,7 @@ export class SettingsEditor2 extends BaseEditor { ...@@ -193,6 +193,7 @@ export class SettingsEditor2 extends BaseEditor {
this.treeDataSource = this.instantiationService.createInstance(SettingsDataSource, this.viewState); this.treeDataSource = this.instantiationService.createInstance(SettingsDataSource, this.viewState);
const renderer = this.instantiationService.createInstance(SettingsRenderer, this.settingsTreeContainer); const renderer = this.instantiationService.createInstance(SettingsRenderer, this.settingsTreeContainer);
this._register(renderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value))); this._register(renderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value)));
this._register(renderer.onDidOpenSettings(() => this.openSettingsFile()));
const treeClass = 'settings-editor-tree'; const treeClass = 'settings-editor-tree';
this.settingsTree = this.instantiationService.createInstance(WorkbenchTree, this.settingsTreeContainer, this.settingsTree = this.instantiationService.createInstance(WorkbenchTree, this.settingsTreeContainer,
...@@ -268,7 +269,7 @@ export class SettingsEditor2 extends BaseEditor { ...@@ -268,7 +269,7 @@ export class SettingsEditor2 extends BaseEditor {
private onShowConfiguredOnlyClicked(): void { private onShowConfiguredOnlyClicked(): void {
this.viewState.showConfiguredOnly = this.showConfiguredSettingsOnlyCheckbox.checked; this.viewState.showConfiguredOnly = this.showConfiguredSettingsOnlyCheckbox.checked;
this.refreshTree(); this.refreshTreeAndMaintainFocus();
// TODO@roblou - This is slow // TODO@roblou - This is slow
if (this.viewState.showConfiguredOnly) { if (this.viewState.showConfiguredOnly) {
...@@ -294,8 +295,7 @@ export class SettingsEditor2 extends BaseEditor { ...@@ -294,8 +295,7 @@ export class SettingsEditor2 extends BaseEditor {
// ConfigurationService displays the error if this fails. // ConfigurationService displays the error if this fails.
// Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change // Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change
this.configurationService.updateValue(key, value, <ConfigurationTarget>this.settingsTargetsWidget.settingsTarget) this.configurationService.updateValue(key, value, <ConfigurationTarget>this.settingsTargetsWidget.settingsTarget)
.then(() => this.refreshTree()) .then(() => this.refreshTreeAndMaintainFocus());
.then(() => this.settingsTree.domFocus());
const reportModifiedProps = { const reportModifiedProps = {
key, key,
...@@ -391,8 +391,26 @@ export class SettingsEditor2 extends BaseEditor { ...@@ -391,8 +391,26 @@ export class SettingsEditor2 extends BaseEditor {
return TPromise.as(null); return TPromise.as(null);
} }
private refreshTree(): TPromise<any> { private refreshTreeAndMaintainFocus(): TPromise<any> {
return this.settingsTree.refresh(); // Sort of a hack to maintain focus on the focused control across a refresh
const focusedRowItem = DOM.findParentWithClass(<HTMLElement>document.activeElement, 'setting-item');
const focusedRowId = focusedRowItem && focusedRowItem.id;
const selection = focusedRowId && document.activeElement.tagName.toLowerCase() === 'input' ?
(<HTMLInputElement>document.activeElement).selectionStart :
null;
return this.settingsTree.refresh().then(() => {
if (focusedRowId) {
const rowSelector = `.setting-item#${focusedRowId}`;
const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} a`);
if (inputElementToFocus) {
inputElementToFocus.focus();
if (typeof selection === 'number') {
(<HTMLInputElement>inputElementToFocus).setSelectionRange(selection, selection);
}
}
}
});
} }
private onSearchInputChanged(): void { private onSearchInputChanged(): void {
...@@ -486,7 +504,7 @@ export class SettingsEditor2 extends BaseEditor { ...@@ -486,7 +504,7 @@ export class SettingsEditor2 extends BaseEditor {
} }
this.searchResultModel.setResult(type, result); this.searchResultModel.setResult(type, result);
return this.refreshTree(); return this.refreshTreeAndMaintainFocus();
}); });
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { Button } from 'vs/base/browser/ui/button/button'; import { Button } from 'vs/base/browser/ui/button/button';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
...@@ -11,6 +12,7 @@ import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; ...@@ -11,6 +12,7 @@ import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { Color } from 'vs/base/common/color'; import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as objects from 'vs/base/common/objects'; import * as objects from 'vs/base/common/objects';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
...@@ -119,7 +121,7 @@ export class SettingsDataSource implements IDataSource { ...@@ -119,7 +121,7 @@ export class SettingsDataSource implements IDataSource {
return <ISettingElement>{ return <ISettingElement>{
type: TreeItemType.setting, type: TreeItemType.setting,
parent: group, parent: group,
id: `${group.id}_${setting.key}`, id: `${group.id}_${setting.key.replace(/\./g, '_')}`,
setting, setting,
displayLabel: displayKeyFormat.label, displayLabel: displayKeyFormat.label,
...@@ -294,7 +296,7 @@ export class SettingsRenderer implements IRenderer { ...@@ -294,7 +296,7 @@ export class SettingsRenderer implements IRenderer {
private measureSettingElementHeight(tree: ITree, element: ISettingElement): number { private measureSettingElementHeight(tree: ITree, element: ISettingElement): number {
const measureHelper = DOM.append(this.measureContainer, $('.setting-measure-helper')); const measureHelper = DOM.append(this.measureContainer, $('.setting-measure-helper'));
const template = this.renderSettingTemplate(measureHelper); const template = this.renderSettingTemplate(tree, measureHelper);
this.renderSettingElement(tree, element, template, true); this.renderSettingElement(tree, element, template, true);
const height = measureHelper.offsetHeight; const height = measureHelper.offsetHeight;
...@@ -320,7 +322,7 @@ export class SettingsRenderer implements IRenderer { ...@@ -320,7 +322,7 @@ export class SettingsRenderer implements IRenderer {
} }
if (templateId === SETTINGS_ELEMENT_TEMPLATE_ID) { if (templateId === SETTINGS_ELEMENT_TEMPLATE_ID) {
return this.renderSettingTemplate(container); return this.renderSettingTemplate(tree, container);
} }
return null; return null;
...@@ -341,7 +343,7 @@ export class SettingsRenderer implements IRenderer { ...@@ -341,7 +343,7 @@ export class SettingsRenderer implements IRenderer {
return template; return template;
} }
private renderSettingTemplate(container: HTMLElement): ISettingItemTemplate { private renderSettingTemplate(tree: ITree, container: HTMLElement): ISettingItemTemplate {
DOM.addClass(container, 'setting-item'); DOM.addClass(container, 'setting-item');
const leftElement = DOM.append(container, $('.setting-item-left')); const leftElement = DOM.append(container, $('.setting-item-left'));
...@@ -375,6 +377,13 @@ export class SettingsRenderer implements IRenderer { ...@@ -375,6 +377,13 @@ export class SettingsRenderer implements IRenderer {
// Prevent clicks from being handled by list // Prevent clicks from being handled by list
toDispose.push(DOM.addDisposableListener(valueElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation())); toDispose.push(DOM.addDisposableListener(valueElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation()));
toDispose.push(DOM.addStandardDisposableListener(valueElement, 'keydown', (e: StandardKeyboardEvent) => {
if (e.keyCode === KeyCode.Escape) {
tree.domFocus();
e.browserEvent.stopPropagation();
}
}));
return template; return template;
} }
......
...@@ -607,7 +607,7 @@ configurationRegistry.registerConfiguration({ ...@@ -607,7 +607,7 @@ configurationRegistry.registerConfiguration({
}, },
'search.enableSearchProviders': { 'search.enableSearchProviders': {
type: 'boolean', type: 'boolean',
default: true, default: false,
description: nls.localize('search.enableSearchProviders', " (Experimental) Controls whether search provider extensions should be enabled.") description: nls.localize('search.enableSearchProviders', " (Experimental) Controls whether search provider extensions should be enabled.")
} }
} }
......
...@@ -6,18 +6,18 @@ ...@@ -6,18 +6,18 @@
import * as assert from 'assert'; import * as assert from 'assert';
import * as path from 'path'; import * as path from 'path';
import * as extfs from 'vs/base/node/extfs'; import { isPromiseCanceledError } from 'vs/base/common/errors';
import { dispose } from 'vs/base/common/lifecycle';
import { joinPath } from 'vs/base/common/resources';
import URI, { UriComponents } from 'vs/base/common/uri'; import URI, { UriComponents } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IRawFileMatch2, IRawSearchQuery, QueryType, ISearchQuery, IPatternInfo, IFileMatch } from 'vs/platform/search/common/search'; import * as extfs from 'vs/base/node/extfs';
import { IFileMatch, IPatternInfo, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats, ISearchQuery, QueryType } from 'vs/platform/search/common/search';
import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/node/extHost.protocol'; import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch';
import { Range } from 'vs/workbench/api/node/extHostTypes';
import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { dispose } from 'vs/base/common/lifecycle';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { Range } from 'vs/workbench/api/node/extHostTypes';
import { joinPath } from 'vs/base/common/resources';
let rpcProtocol: TestRPCProtocol; let rpcProtocol: TestRPCProtocol;
let extHostSearch: ExtHostSearch; let extHostSearch: ExtHostSearch;
...@@ -59,7 +59,8 @@ suite('ExtHostSearch', () => { ...@@ -59,7 +59,8 @@ suite('ExtHostSearch', () => {
await rpcProtocol.sync(); await rpcProtocol.sync();
} }
async function runFileSearch(query: IRawSearchQuery, cancel = false): TPromise<URI[]> { async function runFileSearch(query: IRawSearchQuery, cancel = false): TPromise<{ results: URI[]; stats: ISearchCompleteStats }> {
let stats: ISearchCompleteStats;
try { try {
const p = extHostSearch.$provideFileSearchResults(mockMainThreadSearch.lastHandle, 0, query); const p = extHostSearch.$provideFileSearchResults(mockMainThreadSearch.lastHandle, 0, query);
if (cancel) { if (cancel) {
...@@ -67,7 +68,7 @@ suite('ExtHostSearch', () => { ...@@ -67,7 +68,7 @@ suite('ExtHostSearch', () => {
p.cancel(); p.cancel();
} }
await p; stats = await p;
} catch (err) { } catch (err) {
if (!isPromiseCanceledError(err)) { if (!isPromiseCanceledError(err)) {
await rpcProtocol.sync(); await rpcProtocol.sync();
...@@ -76,10 +77,14 @@ suite('ExtHostSearch', () => { ...@@ -76,10 +77,14 @@ suite('ExtHostSearch', () => {
} }
await rpcProtocol.sync(); await rpcProtocol.sync();
return (<UriComponents[]>mockMainThreadSearch.results).map(r => URI.revive(r)); return {
results: (<UriComponents[]>mockMainThreadSearch.results).map(r => URI.revive(r)),
stats
};
} }
async function runTextSearch(pattern: IPatternInfo, query: IRawSearchQuery, cancel = false): TPromise<IFileMatch[]> { async function runTextSearch(pattern: IPatternInfo, query: IRawSearchQuery, cancel = false): TPromise<{ results: IFileMatch[], stats: ISearchCompleteStats }> {
let stats: ISearchCompleteStats;
try { try {
const p = extHostSearch.$provideTextSearchResults(mockMainThreadSearch.lastHandle, 0, pattern, query); const p = extHostSearch.$provideTextSearchResults(mockMainThreadSearch.lastHandle, 0, pattern, query);
if (cancel) { if (cancel) {
...@@ -87,7 +92,7 @@ suite('ExtHostSearch', () => { ...@@ -87,7 +92,7 @@ suite('ExtHostSearch', () => {
p.cancel(); p.cancel();
} }
await p; stats = await p;
} catch (err) { } catch (err) {
if (!isPromiseCanceledError(err)) { if (!isPromiseCanceledError(err)) {
await rpcProtocol.sync(); await rpcProtocol.sync();
...@@ -96,12 +101,14 @@ suite('ExtHostSearch', () => { ...@@ -96,12 +101,14 @@ suite('ExtHostSearch', () => {
} }
await rpcProtocol.sync(); await rpcProtocol.sync();
return (<IRawFileMatch2[]>mockMainThreadSearch.results).map(r => ({ const results = (<IRawFileMatch2[]>mockMainThreadSearch.results).map(r => ({
...r, ...r,
...{ ...{
resource: URI.revive(r.resource) resource: URI.revive(r.resource)
} }
})); }));
return { results, stats };
} }
setup(() => { setup(() => {
...@@ -153,7 +160,8 @@ suite('ExtHostSearch', () => { ...@@ -153,7 +160,8 @@ suite('ExtHostSearch', () => {
} }
}); });
const results = await runFileSearch(getSimpleQuery()); const { results, stats } = await runFileSearch(getSimpleQuery());
assert(!stats.limitHit);
assert(!results.length); assert(!results.length);
}); });
...@@ -171,18 +179,12 @@ suite('ExtHostSearch', () => { ...@@ -171,18 +179,12 @@ suite('ExtHostSearch', () => {
} }
}); });
const results = await runFileSearch(getSimpleQuery()); const { results, stats } = await runFileSearch(getSimpleQuery());
assert(!stats.limitHit);
assert.equal(results.length, 3); assert.equal(results.length, 3);
compareURIs(results, reportedResults); compareURIs(results, reportedResults);
}); });
// Sibling clauses
// Extra files
// Max result count
// Absolute/relative logic
// Includes/excludes passed to provider correctly
// Provider misbehaves
test('Search canceled', async () => { test('Search canceled', async () => {
let cancelRequested = false; let cancelRequested = false;
await registerTestSearchProvider({ await registerTestSearchProvider({
...@@ -198,7 +200,7 @@ suite('ExtHostSearch', () => { ...@@ -198,7 +200,7 @@ suite('ExtHostSearch', () => {
} }
}); });
const results = await runFileSearch(getSimpleQuery(), true); const { results } = await runFileSearch(getSimpleQuery(), true);
assert(cancelRequested); assert(cancelRequested);
assert(!results.length); assert(!results.length);
}); });
...@@ -376,7 +378,7 @@ suite('ExtHostSearch', () => { ...@@ -376,7 +378,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runFileSearch(query); const { results } = await runFileSearch(query);
compareURIs( compareURIs(
results, results,
[ [
...@@ -436,7 +438,7 @@ suite('ExtHostSearch', () => { ...@@ -436,7 +438,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runFileSearch(query); const { results } = await runFileSearch(query);
compareURIs( compareURIs(
results, results,
[ [
...@@ -479,7 +481,8 @@ suite('ExtHostSearch', () => { ...@@ -479,7 +481,8 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runFileSearch(query); const { results, stats } = await runFileSearch(query);
assert(stats.limitHit, 'Expected to return limitHit');
assert.equal(results.length, 1); assert.equal(results.length, 1);
compareURIs(results, reportedResults.slice(0, 1)); compareURIs(results, reportedResults.slice(0, 1));
assert(wasCanceled, 'Expected to be canceled when hitting limit'); assert(wasCanceled, 'Expected to be canceled when hitting limit');
...@@ -515,12 +518,49 @@ suite('ExtHostSearch', () => { ...@@ -515,12 +518,49 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runFileSearch(query); const { results, stats } = await runFileSearch(query);
assert(stats.limitHit, 'Expected to return limitHit');
assert.equal(results.length, 2); assert.equal(results.length, 2);
compareURIs(results, reportedResults.slice(0, 2)); compareURIs(results, reportedResults.slice(0, 2));
assert(wasCanceled, 'Expected to be canceled when hitting limit'); assert(wasCanceled, 'Expected to be canceled when hitting limit');
}); });
test('provider returns maxResults exactly', async () => {
const reportedResults = [
joinPath(rootFolderA, 'file1.ts'),
joinPath(rootFolderA, 'file2.ts'),
];
let wasCanceled = false;
await registerTestSearchProvider({
provideFileSearchResults(options: vscode.FileSearchOptions, progress: vscode.Progress<string>, token: vscode.CancellationToken): Thenable<void> {
reportedResults.forEach(r => progress.report(path.basename(r.fsPath)));
token.onCancellationRequested(() => wasCanceled = true);
return TPromise.wrap(null);
}
});
const query: ISearchQuery = {
type: QueryType.File,
filePattern: '',
maxResults: 2,
folderQueries: [
{
folder: rootFolderA
}
]
};
const { results, stats } = await runFileSearch(query);
assert(!stats.limitHit, 'Expected not to return limitHit');
assert.equal(results.length, 2);
compareURIs(results, reportedResults);
assert(!wasCanceled, 'Expected not to be canceled when just reaching limit');
});
test('multiroot max results', async () => { test('multiroot max results', async () => {
let cancels = 0; let cancels = 0;
await registerTestSearchProvider({ await registerTestSearchProvider({
...@@ -557,7 +597,7 @@ suite('ExtHostSearch', () => { ...@@ -557,7 +597,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runFileSearch(query); const { results } = await runFileSearch(query);
assert.equal(results.length, 2); // Don't care which 2 we got assert.equal(results.length, 2); // Don't care which 2 we got
assert.equal(cancels, 2, 'Expected all invocations to be canceled when hitting limit'); assert.equal(cancels, 2, 'Expected all invocations to be canceled when hitting limit');
}); });
...@@ -588,7 +628,7 @@ suite('ExtHostSearch', () => { ...@@ -588,7 +628,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runFileSearch(query); const { results } = await runFileSearch(query);
assert.equal(results.length, 1); assert.equal(results.length, 1);
compareURIs(results, reportedResults.slice(2)); compareURIs(results, reportedResults.slice(2));
}); });
...@@ -618,7 +658,7 @@ suite('ExtHostSearch', () => { ...@@ -618,7 +658,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runFileSearch(query); const { results } = await runFileSearch(query);
compareURIs(results, reportedResults); compareURIs(results, reportedResults);
}); });
...@@ -639,7 +679,7 @@ suite('ExtHostSearch', () => { ...@@ -639,7 +679,7 @@ suite('ExtHostSearch', () => {
// }); // });
// const queriedFilePath = queriedFile.fsPath; // const queriedFilePath = queriedFile.fsPath;
// const results = await runFileSearch(getSimpleQuery(queriedFilePath)); // const { results } = await runFileSearch(getSimpleQuery(queriedFilePath));
// assert.equal(results.length, 1); // assert.equal(results.length, 1);
// compareURIs(results, [queriedFile]); // compareURIs(results, [queriedFile]);
// }); // });
...@@ -722,7 +762,8 @@ suite('ExtHostSearch', () => { ...@@ -722,7 +762,8 @@ suite('ExtHostSearch', () => {
} }
}); });
const results = await runTextSearch(getPattern('foo'), getSimpleQuery()); const { results, stats } = await runTextSearch(getPattern('foo'), getSimpleQuery());
assert(!stats.limitHit);
assert(!results.length); assert(!results.length);
}); });
...@@ -739,7 +780,8 @@ suite('ExtHostSearch', () => { ...@@ -739,7 +780,8 @@ suite('ExtHostSearch', () => {
} }
}); });
const results = await runTextSearch(getPattern('foo'), getSimpleQuery()); const { results, stats } = await runTextSearch(getPattern('foo'), getSimpleQuery());
assert(!stats.limitHit);
assertResults(results, providedResults); assertResults(results, providedResults);
}); });
...@@ -903,7 +945,7 @@ suite('ExtHostSearch', () => { ...@@ -903,7 +945,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runTextSearch(getPattern('foo'), query); const { results } = await runTextSearch(getPattern('foo'), query);
assertResults(results, providedResults.slice(1)); assertResults(results, providedResults.slice(1));
}); });
...@@ -975,7 +1017,7 @@ suite('ExtHostSearch', () => { ...@@ -975,7 +1017,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runTextSearch(getPattern('foo'), query); const { results } = await runTextSearch(getPattern('foo'), query);
assertResults(results, [ assertResults(results, [
makeTextResult('folder/fileA.scss'), makeTextResult('folder/fileA.scss'),
makeTextResult('folder/file2.css'), makeTextResult('folder/file2.css'),
...@@ -1009,7 +1051,7 @@ suite('ExtHostSearch', () => { ...@@ -1009,7 +1051,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runTextSearch(getPattern('foo'), query); const { results } = await runTextSearch(getPattern('foo'), query);
assertResults(results, providedResults.slice(1)); assertResults(results, providedResults.slice(1));
}); });
...@@ -1038,7 +1080,8 @@ suite('ExtHostSearch', () => { ...@@ -1038,7 +1080,8 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runTextSearch(getPattern('foo'), query); const { results, stats } = await runTextSearch(getPattern('foo'), query);
assert(stats.limitHit, 'Expected to return limitHit');
assertResults(results, providedResults.slice(0, 1)); assertResults(results, providedResults.slice(0, 1));
assert(wasCanceled, 'Expected to be canceled'); assert(wasCanceled, 'Expected to be canceled');
}); });
...@@ -1069,11 +1112,43 @@ suite('ExtHostSearch', () => { ...@@ -1069,11 +1112,43 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runTextSearch(getPattern('foo'), query); const { results, stats } = await runTextSearch(getPattern('foo'), query);
assert(stats.limitHit, 'Expected to return limitHit');
assertResults(results, providedResults.slice(0, 2)); assertResults(results, providedResults.slice(0, 2));
assert(wasCanceled, 'Expected to be canceled'); assert(wasCanceled, 'Expected to be canceled');
}); });
test('provider returns maxResults exactly', async () => {
const providedResults: vscode.TextSearchResult[] = [
makeTextResult('file1.ts'),
makeTextResult('file2.ts')
];
let wasCanceled = false;
await registerTestSearchProvider({
provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Thenable<void> {
token.onCancellationRequested(() => wasCanceled = true);
providedResults.forEach(r => progress.report(r));
return TPromise.wrap(null);
}
});
const query: ISearchQuery = {
type: QueryType.Text,
maxResults: 2,
folderQueries: [
{ folder: rootFolderA }
]
};
const { results, stats } = await runTextSearch(getPattern('foo'), query);
assert(!stats.limitHit, 'Expected not to return limitHit');
assertResults(results, providedResults);
assert(!wasCanceled, 'Expected not to be canceled');
});
test('multiroot max results', async () => { test('multiroot max results', async () => {
let cancels = 0; let cancels = 0;
await registerTestSearchProvider({ await registerTestSearchProvider({
...@@ -1101,7 +1176,7 @@ suite('ExtHostSearch', () => { ...@@ -1101,7 +1176,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runTextSearch(getPattern('foo'), query); const { results } = await runTextSearch(getPattern('foo'), query);
assert.equal(results.length, 2); assert.equal(results.length, 2);
assert.equal(cancels, 2); assert.equal(cancels, 2);
}); });
...@@ -1128,7 +1203,7 @@ suite('ExtHostSearch', () => { ...@@ -1128,7 +1203,7 @@ suite('ExtHostSearch', () => {
] ]
}; };
const results = await runTextSearch(getPattern('foo'), query); const { results } = await runTextSearch(getPattern('foo'), query);
assertResults(results, providedResults, fancySchemeFolderA); assertResults(results, providedResults, fancySchemeFolderA);
}); });
}); });
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册