提交 497ea886 编写于 作者: R Rob Lourens

#56137 - remove more TPromise#cancel from search

上级 5f513e74
...@@ -13,6 +13,7 @@ import { URI as uri, UriComponents } from 'vs/base/common/uri'; ...@@ -13,6 +13,7 @@ import { URI as uri, UriComponents } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IFilesConfiguration } from 'vs/platform/files/common/files'; import { IFilesConfiguration } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { CancellationToken } from 'vs/base/common/cancellation';
export const VIEW_ID = 'workbench.view.search'; export const VIEW_ID = 'workbench.view.search';
...@@ -24,7 +25,7 @@ export const ISearchService = createDecorator<ISearchService>('searchService'); ...@@ -24,7 +25,7 @@ export const ISearchService = createDecorator<ISearchService>('searchService');
*/ */
export interface ISearchService { export interface ISearchService {
_serviceBrand: any; _serviceBrand: any;
search(query: ISearchQuery, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete>; search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete>;
extendQuery(query: ISearchQuery): void; extendQuery(query: ISearchQuery): void;
clearCache(cacheKey: string): TPromise<void>; clearCache(cacheKey: string): TPromise<void>;
registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable; registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable;
...@@ -55,7 +56,7 @@ export enum SearchProviderType { ...@@ -55,7 +56,7 @@ export enum SearchProviderType {
} }
export interface ISearchResultProvider { export interface ISearchResultProvider {
search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete>; search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete>;
clearCache(cacheKey: string): TPromise<void>; clearCache(cacheKey: string): TPromise<void>;
} }
......
...@@ -13,6 +13,7 @@ import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISea ...@@ -13,6 +13,7 @@ import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISea
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../node/extHost.protocol'; import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../node/extHost.protocol';
import { CancellationToken } from 'vs/base/common/cancellation';
@extHostNamedCustomer(MainContext.MainThreadSearch) @extHostNamedCustomer(MainContext.MainThreadSearch)
export class MainThreadSearch implements MainThreadSearchShape { export class MainThreadSearch implements MainThreadSearchShape {
...@@ -106,34 +107,28 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { ...@@ -106,34 +107,28 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
dispose(this._registrations); dispose(this._registrations);
} }
search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete> { search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete> {
if (isFalsyOrEmpty(query.folderQueries)) { if (isFalsyOrEmpty(query.folderQueries)) {
return TPromise.as(undefined); return TPromise.as(undefined);
} }
let outer: TPromise; const search = new SearchOperation(onProgress);
this._searches.set(search.id, search);
return new TPromise((resolve, reject) => {
const search = new SearchOperation(onProgress); const searchP = query.type === QueryType.File
this._searches.set(search.id, search); ? this._proxy.$provideFileSearchResults(this._handle, search.id, query)
: this._proxy.$provideTextSearchResults(this._handle, search.id, query.contentPattern, query);
outer = query.type === QueryType.File if (token) {
? this._proxy.$provideFileSearchResults(this._handle, search.id, query) token.onCancellationRequested(() => searchP.cancel());
: this._proxy.$provideTextSearchResults(this._handle, search.id, query.contentPattern, query); }
outer.then((result: ISearchCompleteStats) => { return searchP.then((result: ISearchCompleteStats) => {
this._searches.delete(search.id); this._searches.delete(search.id);
resolve(({ results: values(search.matches), stats: result.stats, limitHit: result.limitHit })); return { results: values(search.matches), stats: result.stats, limitHit: result.limitHit };
}, err => { }, err => {
this._searches.delete(search.id); this._searches.delete(search.id);
reject(err); return TPromise.wrapError(err);
});
}, () => {
if (outer) {
outer.cancel();
}
}); });
} }
......
...@@ -24,12 +24,13 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten ...@@ -24,12 +24,13 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol'; import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
@extHostNamedCustomer(MainContext.MainThreadWorkspace) @extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape { export class MainThreadWorkspace implements MainThreadWorkspaceShape {
private readonly _toDispose: IDisposable[] = []; private readonly _toDispose: IDisposable[] = [];
private readonly _activeSearches: { [id: number]: TPromise<any> } = Object.create(null); private readonly _activeCancelTokens: { [id: number]: CancellationTokenSource } = Object.create(null);
private readonly _proxy: ExtHostWorkspaceShape; private readonly _proxy: ExtHostWorkspaceShape;
constructor( constructor(
...@@ -51,9 +52,9 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -51,9 +52,9 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
dispose(): void { dispose(): void {
dispose(this._toDispose); dispose(this._toDispose);
for (let requestId in this._activeSearches) { for (let requestId in this._activeCancelTokens) {
const search = this._activeSearches[requestId]; const tokenSource = this._activeCancelTokens[requestId];
search.cancel(); tokenSource.cancel();
} }
} }
...@@ -157,7 +158,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -157,7 +158,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
this._searchService.extendQuery(query); this._searchService.extendQuery(query);
const search = this._searchService.search(query).then(result => { const tokenSource = new CancellationTokenSource();
const search = this._searchService.search(query, tokenSource.token).then(result => {
return result.results.map(m => m.resource); return result.results.map(m => m.resource);
}, err => { }, err => {
if (!isPromiseCanceledError(err)) { if (!isPromiseCanceledError(err)) {
...@@ -166,8 +168,11 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -166,8 +168,11 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return undefined; return undefined;
}); });
this._activeSearches[requestId] = search; this._activeCancelTokens[requestId] = tokenSource;
const onDone = () => delete this._activeSearches[requestId]; const onDone = () => {
tokenSource.dispose();
delete this._activeCancelTokens[requestId];
};
search.then(onDone, onDone); search.then(onDone, onDone);
return search; return search;
...@@ -186,13 +191,12 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -186,13 +191,12 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
} }
}; };
const search = this._searchService.search(query, onProgress).then( const tokenSource = new CancellationTokenSource();
const search = this._searchService.search(query, tokenSource.token, onProgress).then(
() => { () => {
delete this._activeSearches[requestId];
return null; return null;
}, },
err => { err => {
delete this._activeSearches[requestId];
if (!isPromiseCanceledError(err)) { if (!isPromiseCanceledError(err)) {
return TPromise.wrapError(err); return TPromise.wrapError(err);
} }
...@@ -200,20 +204,27 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -200,20 +204,27 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return undefined; return undefined;
}); });
this._activeSearches[requestId] = search; this._activeCancelTokens[requestId] = tokenSource;
const onDone = () => {
tokenSource.dispose();
delete this._activeCancelTokens[requestId];
};
search.then(onDone, onDone);
return search; return search;
} }
$checkExists(query: ISearchQuery, requestId: number): TPromise<boolean> { $checkExists(query: ISearchQuery, requestId: number): TPromise<boolean> {
query.exists = true; query.exists = true;
const search = this._searchService.search(query).then(
const tokenSource = new CancellationTokenSource();
const search = this._searchService.search(query, tokenSource.token).then(
result => { result => {
delete this._activeSearches[requestId]; delete this._activeCancelTokens[requestId];
return result.limitHit; return result.limitHit;
}, },
err => { err => {
delete this._activeSearches[requestId]; delete this._activeCancelTokens[requestId];
if (!isPromiseCanceledError(err)) { if (!isPromiseCanceledError(err)) {
return TPromise.wrapError(err); return TPromise.wrapError(err);
} }
...@@ -221,16 +232,16 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { ...@@ -221,16 +232,16 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return undefined; return undefined;
}); });
this._activeSearches[requestId] = search; this._activeCancelTokens[requestId] = tokenSource;
return search; return search;
} }
$cancelSearch(requestId: number): Thenable<boolean> { $cancelSearch(requestId: number): Thenable<boolean> {
const search = this._activeSearches[requestId]; const tokenSource = this._activeCancelTokens[requestId];
if (search) { if (tokenSource) {
delete this._activeSearches[requestId]; delete this._activeCancelTokens[requestId];
search.cancel(); tokenSource.cancel();
return TPromise.as(true); return TPromise.as(true);
} }
return undefined; return undefined;
......
...@@ -98,19 +98,15 @@ export class ExtHostSearch implements ExtHostSearchShape { ...@@ -98,19 +98,15 @@ export class ExtHostSearch implements ExtHostSearchShape {
}); });
} else { } else {
const indexProvider = this._fileIndexProvider.get(handle); const indexProvider = this._fileIndexProvider.get(handle);
if (indexProvider) { return this._fileIndexSearchManager.fileSearch(query, indexProvider, batch => {
return this._fileIndexSearchManager.fileSearch(query, indexProvider, batch => { this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); }).then(null, err => {
}).then(null, err => { if (!isPromiseCanceledError(err)) {
if (!isPromiseCanceledError(err)) { throw err;
throw err; }
}
return null; return null;
}); });
} else {
throw new Error('something went wrong');
}
} }
} }
...@@ -300,36 +296,31 @@ class BatchedCollector<T> { ...@@ -300,36 +296,31 @@ class BatchedCollector<T> {
class TextSearchEngine { class TextSearchEngine {
private activeCancellationTokens = new Set<CancellationTokenSource>();
private collector: TextSearchResultsCollector; private collector: TextSearchResultsCollector;
private isLimitHit: boolean; private isLimitHit: boolean;
private resultCount = 0; private resultCount = 0;
private isCanceled: boolean;
constructor(private pattern: IPatternInfo, private config: ISearchQuery, private provider: vscode.TextSearchProvider, private _extfs: typeof extfs) { constructor(private pattern: IPatternInfo, private config: ISearchQuery, private provider: vscode.TextSearchProvider, private _extfs: typeof extfs) {
} }
public cancel(): void {
this.isCanceled = true;
this.activeCancellationTokens.forEach(t => t.cancel());
this.activeCancellationTokens = new Set();
}
public search(onProgress: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> { public search(onProgress: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> {
const folderQueries = this.config.folderQueries; const folderQueries = this.config.folderQueries;
const tokenSource = new CancellationTokenSource();
return new TPromise<ISearchCompleteStats>((resolve, reject) => { return new TPromise<ISearchCompleteStats>((resolve, reject) => {
this.collector = new TextSearchResultsCollector(onProgress); this.collector = new TextSearchResultsCollector(onProgress);
let isCanceled = false;
const onResult = (match: vscode.TextSearchResult, folderIdx: number) => { const onResult = (match: vscode.TextSearchResult, folderIdx: number) => {
if (this.isCanceled) { if (isCanceled) {
return; return;
} }
if (this.resultCount >= this.config.maxResults) { if (this.resultCount >= this.config.maxResults) {
this.isLimitHit = true; this.isLimitHit = true;
this.cancel(); isCanceled = true;
tokenSource.cancel();
} }
if (!this.isLimitHit) { if (!this.isLimitHit) {
...@@ -340,8 +331,9 @@ class TextSearchEngine { ...@@ -340,8 +331,9 @@ class TextSearchEngine {
// For each root folder // For each root folder
TPromise.join(folderQueries.map((fq, i) => { TPromise.join(folderQueries.map((fq, i) => {
return this.searchInFolder(fq, r => onResult(r, i)); return this.searchInFolder(fq, r => onResult(r, i), tokenSource.token);
})).then(() => { })).then(() => {
tokenSource.dispose();
this.collector.flush(); this.collector.flush();
resolve({ resolve({
limitHit: this.isLimitHit, limitHit: this.isLimitHit,
...@@ -350,17 +342,20 @@ class TextSearchEngine { ...@@ -350,17 +342,20 @@ class TextSearchEngine {
} }
}); });
}, (errs: Error[]) => { }, (errs: Error[]) => {
tokenSource.dispose();
const errMsg = errs const errMsg = errs
.map(err => toErrorMessage(err)) .map(err => toErrorMessage(err))
.filter(msg => !!msg)[0]; .filter(msg => !!msg)[0];
reject(new Error(errMsg)); reject(new Error(errMsg));
}); });
}, () => {
// From IPC
tokenSource.cancel();
}); });
} }
private searchInFolder(folderQuery: IFolderQuery<URI>, onResult: (result: vscode.TextSearchResult) => void): TPromise<void> { private searchInFolder(folderQuery: IFolderQuery<URI>, onResult: (result: vscode.TextSearchResult) => void, token: CancellationToken): TPromise<void> {
let cancellation = new CancellationTokenSource();
return new TPromise((resolve, reject) => { return new TPromise((resolve, reject) => {
const queryTester = new QueryGlobTester(this.config, folderQuery); const queryTester = new QueryGlobTester(this.config, folderQuery);
...@@ -385,22 +380,14 @@ class TextSearchEngine { ...@@ -385,22 +380,14 @@ class TextSearchEngine {
const searchOptions = this.getSearchOptionsForFolder(folderQuery); const searchOptions = this.getSearchOptionsForFolder(folderQuery);
new TPromise(resolve => process.nextTick(resolve)) new TPromise(resolve => process.nextTick(resolve))
.then(() => { .then(() => {
this.activeCancellationTokens.add(cancellation); return this.provider.provideTextSearchResults(patternInfoToQuery(this.pattern), searchOptions, progress, token);
return this.provider.provideTextSearchResults(patternInfoToQuery(this.pattern), searchOptions, progress, cancellation.token);
}) })
.then(() => { .then(() => {
this.activeCancellationTokens.delete(cancellation);
return TPromise.join(testingPs); return TPromise.join(testingPs);
}) })
.then( .then(
() => { () => resolve(null),
cancellation.dispose(); reject);
resolve(null);
},
err => {
cancellation.dispose();
reject(err);
});
}); });
} }
......
...@@ -172,8 +172,7 @@ export class OpenFileHandler extends QuickOpenHandler { ...@@ -172,8 +172,7 @@ export class OpenFileHandler extends QuickOpenHandler {
return TPromise.wrap(<ISearchComplete>{ results: [{ resource: result }] }); return TPromise.wrap(<ISearchComplete>{ results: [{ resource: result }] });
} }
// TODO@Rob support cancellation return this.searchService.search(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token);
return this.searchService.search(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions));
}).then(complete => { }).then(complete => {
const results: QuickOpenEntry[] = []; const results: QuickOpenEntry[] = [];
for (let i = 0; i < complete.results.length; i++) { for (let i = 0; i < complete.results.length; i++) {
......
...@@ -24,6 +24,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; ...@@ -24,6 +24,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
export class Match { export class Match {
...@@ -733,7 +734,7 @@ export class SearchModel extends Disposable { ...@@ -733,7 +734,7 @@ export class SearchModel extends Disposable {
private readonly _onReplaceTermChanged: Emitter<void> = this._register(new Emitter<void>()); private readonly _onReplaceTermChanged: Emitter<void> = this._register(new Emitter<void>());
public readonly onReplaceTermChanged: Event<void> = this._onReplaceTermChanged.event; public readonly onReplaceTermChanged: Event<void> = this._onReplaceTermChanged.event;
private currentRequest: TPromise<ISearchComplete>; private currentCancelTokenSource: CancellationTokenSource;
constructor(@ISearchService private searchService: ISearchService, @ITelemetryService private telemetryService: ITelemetryService, @IInstantiationService private instantiationService: IInstantiationService) { constructor(@ISearchService private searchService: ISearchService, @ITelemetryService private telemetryService: ITelemetryService, @IInstantiationService private instantiationService: IInstantiationService) {
super(); super();
...@@ -778,7 +779,8 @@ export class SearchModel extends Disposable { ...@@ -778,7 +779,8 @@ export class SearchModel extends Disposable {
const progressEmitter = new Emitter<void>(); const progressEmitter = new Emitter<void>();
this._replacePattern = new ReplacePattern(this._replaceString, this._searchQuery.contentPattern); this._replacePattern = new ReplacePattern(this._replaceString, this._searchQuery.contentPattern);
this.currentRequest = this.searchService.search(this._searchQuery, p => { const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource();
const currentRequest = this.searchService.search(this._searchQuery, this.currentCancelTokenSource.token, p => {
progressEmitter.fire(); progressEmitter.fire();
this.onSearchProgress(p); this.onSearchProgress(p);
...@@ -787,7 +789,10 @@ export class SearchModel extends Disposable { ...@@ -787,7 +789,10 @@ export class SearchModel extends Disposable {
} }
}); });
const onDone = fromPromise(this.currentRequest); const dispose = () => tokenSource.dispose();
currentRequest.then(dispose, dispose);
const onDone = fromPromise(currentRequest);
const onFirstRender = anyEvent<any>(onDone, progressEmitter.event); const onFirstRender = anyEvent<any>(onDone, progressEmitter.event);
const onFirstRenderStopwatch = stopwatch(onFirstRender); const onFirstRenderStopwatch = stopwatch(onFirstRender);
/* __GDPR__ /* __GDPR__
...@@ -807,18 +812,14 @@ export class SearchModel extends Disposable { ...@@ -807,18 +812,14 @@ export class SearchModel extends Disposable {
*/ */
onDoneStopwatch(duration => this.telemetryService.publicLog('searchResultsFinished', { duration })); onDoneStopwatch(duration => this.telemetryService.publicLog('searchResultsFinished', { duration }));
const currentRequest = this.currentRequest; currentRequest.then(
this.currentRequest.then(
value => this.onSearchCompleted(value, Date.now() - start), value => this.onSearchCompleted(value, Date.now() - start),
e => this.onSearchError(e, Date.now() - start)); e => this.onSearchError(e, Date.now() - start));
// this.currentRequest may be completed (and nulled) immediately
return currentRequest; return currentRequest;
} }
private onSearchCompleted(completed: ISearchComplete, duration: number): ISearchComplete { private onSearchCompleted(completed: ISearchComplete, duration: number): ISearchComplete {
this.currentRequest = null;
const options: IPatternInfo = objects.assign({}, this._searchQuery.contentPattern); const options: IPatternInfo = objects.assign({}, this._searchQuery.contentPattern);
delete options.pattern; delete options.pattern;
...@@ -866,9 +867,8 @@ export class SearchModel extends Disposable { ...@@ -866,9 +867,8 @@ export class SearchModel extends Disposable {
} }
public cancelSearch(): boolean { public cancelSearch(): boolean {
if (this.currentRequest) { if (this.currentCancelTokenSource) {
this.currentRequest.cancel(); this.currentCancelTokenSource.cancel();
this.currentRequest = null;
return true; return true;
} }
return false; return false;
......
...@@ -20,6 +20,7 @@ import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchPro ...@@ -20,6 +20,7 @@ import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchPro
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { SearchModel } from 'vs/workbench/parts/search/common/searchModel'; import { SearchModel } from 'vs/workbench/parts/search/common/searchModel';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
const nullEvent = new class { const nullEvent = new class {
...@@ -83,7 +84,7 @@ suite('SearchModel', () => { ...@@ -83,7 +84,7 @@ suite('SearchModel', () => {
function searchServiceWithResults(results: IFileMatch[], complete: ISearchComplete = null): ISearchService { function searchServiceWithResults(results: IFileMatch[], complete: ISearchComplete = null): ISearchService {
return <ISearchService>{ return <ISearchService>{
search(query: ISearchQuery, onProgress: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> { search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> {
return new TPromise(resolve => { return new TPromise(resolve => {
process.nextTick(() => { process.nextTick(() => {
results.forEach(onProgress); results.forEach(onProgress);
...@@ -96,7 +97,7 @@ suite('SearchModel', () => { ...@@ -96,7 +97,7 @@ suite('SearchModel', () => {
function searchServiceWithError(error: Error): ISearchService { function searchServiceWithError(error: Error): ISearchService {
return <ISearchService>{ return <ISearchService>{
search(query: ISearchQuery, onProgress: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> { search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> {
return new TPromise((resolve, reject) => { return new TPromise((resolve, reject) => {
reject(error); reject(error);
}); });
...@@ -104,6 +105,22 @@ suite('SearchModel', () => { ...@@ -104,6 +105,22 @@ suite('SearchModel', () => {
}; };
} }
function canceleableSearchService(tokenSource: CancellationTokenSource): ISearchService {
return <ISearchService>{
search(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): TPromise<ISearchComplete> {
if (token) {
token.onCancellationRequested(() => tokenSource.cancel());
}
return new TPromise(resolve => {
process.nextTick(() => {
resolve(<any>{});
});
});
}
};
}
test('Search Model: Search adds to results', async () => { test('Search Model: Search adds to results', async () => {
let results = [ let results = [
aRawMatch('file://c:/1', aRawMatch('file://c:/1',
...@@ -257,15 +274,15 @@ suite('SearchModel', () => { ...@@ -257,15 +274,15 @@ suite('SearchModel', () => {
}); });
test('Search Model: Previous search is cancelled when new search is called', async () => { test('Search Model: Previous search is cancelled when new search is called', async () => {
let target = sinon.spy(); const tokenSource = new CancellationTokenSource();
instantiationService.stub(ISearchService, 'search', new DeferredTPromise(target)); instantiationService.stub(ISearchService, canceleableSearchService(tokenSource));
let testObject: SearchModel = instantiationService.createInstance(SearchModel); const testObject: SearchModel = instantiationService.createInstance(SearchModel);
testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries }); testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries });
instantiationService.stub(ISearchService, searchServiceWithResults([])); instantiationService.stub(ISearchService, searchServiceWithResults([]));
testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries }); testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries });
assert.ok(target.calledOnce); assert.ok(tokenSource.token.isCancellationRequested);
}); });
test('getReplaceString returns proper replace string for regExpressions', async () => { test('getReplaceString returns proper replace string for regExpressions', async () => {
......
...@@ -30,6 +30,8 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un ...@@ -30,6 +30,8 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { IRawSearch, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from './search'; import { IRawSearch, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from './search';
import { ISearchChannel, SearchChannelClient } from './searchIpc'; import { ISearchChannel, SearchChannelClient } from './searchIpc';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
export class SearchService extends Disposable implements ISearchService { export class SearchService extends Disposable implements ISearchService {
public _serviceBrand: any; public _serviceBrand: any;
...@@ -92,65 +94,62 @@ export class SearchService extends Disposable implements ISearchService { ...@@ -92,65 +94,62 @@ export class SearchService extends Disposable implements ISearchService {
} }
} }
public search(query: ISearchQuery, onProgress?: (item: ISearchProgressItem) => void): TPromise<ISearchComplete> { public search(query: ISearchQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): TPromise<ISearchComplete> {
let combinedPromise: TPromise<void>; // Get local results from dirty/untitled
const localResults = this.getLocalResults(query);
return new TPromise<ISearchComplete>((onComplete, onError) => { if (onProgress) {
localResults.values().filter((res) => !!res).forEach(onProgress);
// Get local results from dirty/untitled }
const localResults = this.getLocalResults(query);
if (onProgress) {
localResults.values().filter((res) => !!res).forEach(onProgress);
}
this.logService.trace('SearchService#search', JSON.stringify(query)); this.logService.trace('SearchService#search', JSON.stringify(query));
const onProviderProgress = progress => { const onProviderProgress = progress => {
if (progress.resource) { if (progress.resource) {
// Match // Match
if (!localResults.has(progress.resource) && onProgress) { // don't override local results if (!localResults.has(progress.resource) && onProgress) { // don't override local results
onProgress(progress); onProgress(progress);
}
} else if (onProgress) {
// Progress
onProgress(<IProgress>progress);
} }
} else if (onProgress) {
// Progress
onProgress(<IProgress>progress);
}
if (progress.message) { if (progress.message) {
this.logService.debug('SearchService#search', progress.message); this.logService.debug('SearchService#search', progress.message);
} }
}; };
const schemesInQuery = this.getSchemesInQuery(query); const schemesInQuery = this.getSchemesInQuery(query);
const providerActivations: TPromise<any>[] = [TPromise.wrap(null)]; const providerActivations: TPromise<any>[] = [TPromise.wrap(null)];
schemesInQuery.forEach(scheme => providerActivations.push(this.extensionService.activateByEvent(`onSearch:${scheme}`))); schemesInQuery.forEach(scheme => providerActivations.push(this.extensionService.activateByEvent(`onSearch:${scheme}`)));
const providerPromise = TPromise.join(providerActivations) const providerPromise = TPromise.join(providerActivations)
.then(() => this.extensionService.whenInstalledExtensionsRegistered()) .then(() => this.extensionService.whenInstalledExtensionsRegistered())
.then(() => this.searchWithProviders(query, onProviderProgress)) .then(() => this.searchWithProviders(query, onProviderProgress, token))
.then(completes => { .then(completes => {
completes = completes.filter(c => !!c); completes = completes.filter(c => !!c);
if (!completes.length) { if (!completes.length) {
return null; return null;
} }
return <ISearchComplete>{ return <ISearchComplete>{
limitHit: completes[0] && completes[0].limitHit, limitHit: completes[0] && completes[0].limitHit,
stats: completes[0].stats, stats: completes[0].stats,
results: arrays.flatten(completes.map(c => c.results)) results: arrays.flatten(completes.map(c => c.results))
}; };
}, errs => { }, errs => {
if (!Array.isArray(errs)) { if (!Array.isArray(errs)) {
errs = [errs]; errs = [errs];
} }
errs = errs.filter(e => !!e); errs = errs.filter(e => !!e);
return TPromise.wrapError(errs[0]); return TPromise.wrapError(errs[0]);
}); });
combinedPromise = providerPromise.then(value => { return new TPromise((c, e) => {
providerPromise.then(value => {
const values = [value]; const values = [value];
const result: ISearchComplete = { const result: ISearchComplete = {
...@@ -177,10 +176,11 @@ export class SearchService extends Disposable implements ISearchService { ...@@ -177,10 +176,11 @@ export class SearchService extends Disposable implements ISearchService {
} }
return result; return result;
}).then(c, e);
}, () => {
// Need the OpenAnythingHandler to stop trying to cancel this promise, https://github.com/Microsoft/vscode/issues/56137
});
}).then(onComplete, onError);
}, () => combinedPromise && combinedPromise.cancel());
} }
private getSchemesInQuery(query: ISearchQuery): Set<string> { private getSchemesInQuery(query: ISearchQuery): Set<string> {
...@@ -196,7 +196,7 @@ export class SearchService extends Disposable implements ISearchService { ...@@ -196,7 +196,7 @@ export class SearchService extends Disposable implements ISearchService {
return schemes; return schemes;
} }
private searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void) { private searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) {
const e2eSW = StopWatch.create(false); const e2eSW = StopWatch.create(false);
const diskSearchQueries: IFolderQuery[] = []; const diskSearchQueries: IFolderQuery[] = [];
...@@ -219,7 +219,7 @@ export class SearchService extends Disposable implements ISearchService { ...@@ -219,7 +219,7 @@ export class SearchService extends Disposable implements ISearchService {
} }
}; };
searchPs.push(provider.search(oneFolderQuery, onProviderProgress)); searchPs.push(provider.search(oneFolderQuery, onProviderProgress, token));
} }
}); });
...@@ -234,7 +234,7 @@ export class SearchService extends Disposable implements ISearchService { ...@@ -234,7 +234,7 @@ export class SearchService extends Disposable implements ISearchService {
extraFileResources: diskSearchExtraFileResources extraFileResources: diskSearchExtraFileResources
}; };
searchPs.push(this.diskSearch.search(diskSearchQuery, onProviderProgress)); searchPs.push(this.diskSearch.search(diskSearchQuery, onProviderProgress, token));
} }
return TPromise.join(searchPs).then(completes => { return TPromise.join(searchPs).then(completes => {
...@@ -447,7 +447,7 @@ export class DiskSearch implements ISearchResultProvider { ...@@ -447,7 +447,7 @@ export class DiskSearch implements ISearchResultProvider {
this.raw = new SearchChannelClient(channel); this.raw = new SearchChannelClient(channel);
} }
public search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete> { public search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete> {
const folderQueries = query.folderQueries || []; const folderQueries = query.folderQueries || [];
return TPromise.join(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath))) return TPromise.join(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath)))
.then(exists => { .then(exists => {
...@@ -461,7 +461,7 @@ export class DiskSearch implements ISearchResultProvider { ...@@ -461,7 +461,7 @@ export class DiskSearch implements ISearchResultProvider {
event = this.raw.textSearch(rawSearch); event = this.raw.textSearch(rawSearch);
} }
return DiskSearch.collectResultsFromEvent(event, onProgress); return DiskSearch.collectResultsFromEvent(event, onProgress, token);
}); });
} }
...@@ -507,11 +507,21 @@ export class DiskSearch implements ISearchResultProvider { ...@@ -507,11 +507,21 @@ export class DiskSearch implements ISearchResultProvider {
return rawSearch; return rawSearch;
} }
public static collectResultsFromEvent(event: Event<ISerializedSearchProgressItem | ISerializedSearchComplete>, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete> { public static collectResultsFromEvent(event: Event<ISerializedSearchProgressItem | ISerializedSearchComplete>, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): TPromise<ISearchComplete> {
let result: IFileMatch[] = []; let result: IFileMatch[] = [];
let listener: IDisposable; let listener: IDisposable;
return new TPromise<ISearchComplete>((c, e) => { return new TPromise<ISearchComplete>((c, e) => {
if (token) {
token.onCancellationRequested(() => {
if (listener) {
listener.dispose();
}
e(canceled());
});
}
listener = event(ev => { listener = event(ev => {
if (isSerializedSearchComplete(ev)) { if (isSerializedSearchComplete(ev)) {
if (isSerializedSearchSuccess(ev)) { if (isSerializedSearchSuccess(ev)) {
...@@ -551,8 +561,7 @@ export class DiskSearch implements ISearchResultProvider { ...@@ -551,8 +561,7 @@ export class DiskSearch implements ISearchResultProvider {
} }
} }
}); });
}, });
() => listener && listener.dispose());
} }
private static createFileMatch(data: ISerializedFileMatch): FileMatch { private static createFileMatch(data: ISerializedFileMatch): FileMatch {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册