diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts index 3418ddaf1a2..13d824066d0 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts @@ -39,6 +39,7 @@ import './mainThreadOutputService'; import './mainThreadProgress'; import './mainThreadQuickOpen'; import './mainThreadSCM'; +import './mainThreadSearch'; import './mainThreadSaveParticipant'; import './mainThreadStatusBar'; import './mainThreadStorage'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts index 8517ec69fe6..d48c4b7ba43 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts @@ -4,28 +4,23 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI, { UriComponents } from 'vs/base/common/uri'; -import { TPromise, PPromise } from 'vs/base/common/winjs.base'; -import { ExtHostContext, MainContext, IExtHostContext, MainThreadFileSystemShape, ExtHostFileSystemShape, IFileChangeDto } from '../node/extHost.protocol'; -import { IFileService, IStat, IFileChange, ISimpleReadWriteProvider, IFileSystemProviderBase, FileOpenFlags } from 'vs/platform/files/common/files'; +import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; +import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { FileOpenFlags, IFileChange, IFileService, IFileSystemProviderBase, ISimpleReadWriteProvider, IStat } from 'vs/platform/files/common/files'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { ISearchResultProvider, ISearchQuery, ISearchComplete, ISearchProgressItem, QueryType, IFileMatch, ISearchService, ILineMatch } from 'vs/platform/search/common/search'; -import { values } from 'vs/base/common/map'; -import { isFalsyOrEmpty } from 'vs/base/common/arrays'; +import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../node/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadFileSystem) export class MainThreadFileSystem implements MainThreadFileSystemShape { private readonly _proxy: ExtHostFileSystemShape; private readonly _fileProvider = new Map(); - private readonly _searchProvider = new Map(); constructor( extHostContext: IExtHostContext, - @IFileService private readonly _fileService: IFileService, - @ISearchService private readonly _searchService: ISearchService + @IFileService private readonly _fileService: IFileService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystem); } @@ -38,27 +33,14 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { $registerFileSystemProvider(handle: number, scheme: string): void { this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, handle, this._proxy)); } - - $registerSearchProvider(handle: number, scheme: string): void { - this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, scheme, handle, this._proxy)); - } - $unregisterProvider(handle: number): void { dispose(this._fileProvider.get(handle)); this._fileProvider.delete(handle); - - dispose(this._searchProvider.get(handle)); - this._searchProvider.delete(handle); } $onFileSystemChange(handle: number, changes: IFileChangeDto[]): void { this._fileProvider.get(handle).$onFileSystemChange(changes); } - // --- search - - $handleFindMatch(handle: number, session, data: UriComponents | [UriComponents, ILineMatch]): void { - this._searchProvider.get(handle).handleFindMatch(session, data); - } } class RemoteFileSystemProvider implements ISimpleReadWriteProvider, IFileSystemProviderBase { @@ -121,105 +103,3 @@ class RemoteFileSystemProvider implements ISimpleReadWriteProvider, IFileSystemP return this._proxy.$readdir(this._handle, resource); } } - -class SearchOperation { - - private static _idPool = 0; - - constructor( - readonly progress: (match: IFileMatch) => any, - readonly id: number = ++SearchOperation._idPool, - readonly matches = new Map() - ) { - // - } - - addMatch(resource: URI, match: ILineMatch): void { - if (!this.matches.has(resource.toString())) { - this.matches.set(resource.toString(), { resource, lineMatches: [] }); - } - if (match) { - this.matches.get(resource.toString()).lineMatches.push(match); - } - this.progress(this.matches.get(resource.toString())); - } -} - -class RemoteSearchProvider implements ISearchResultProvider { - - private readonly _registrations: IDisposable[]; - private readonly _searches = new Map(); - - - constructor( - searchService: ISearchService, - private readonly _scheme: string, - private readonly _handle: number, - private readonly _proxy: ExtHostFileSystemShape - ) { - this._registrations = [searchService.registerSearchResultProvider(this)]; - } - - dispose(): void { - dispose(this._registrations); - } - - search(query: ISearchQuery): PPromise { - - if (isFalsyOrEmpty(query.folderQueries)) { - return PPromise.as(undefined); - } - - let includes = { ...query.includePattern }; - let excludes = { ...query.excludePattern }; - - for (const folderQuery of query.folderQueries) { - if (folderQuery.folder.scheme === this._scheme) { - includes = { ...includes, ...folderQuery.includePattern }; - excludes = { ...excludes, ...folderQuery.excludePattern }; - } - } - - let outer: TPromise; - - return new PPromise((resolve, reject, report) => { - - const search = new SearchOperation(report); - this._searches.set(search.id, search); - - outer = query.type === QueryType.File - ? this._proxy.$provideFileSearchResults(this._handle, search.id, query.filePattern) - : this._proxy.$provideTextSearchResults(this._handle, search.id, query.contentPattern, { excludes: Object.keys(excludes), includes: Object.keys(includes) }); - - outer.then(() => { - this._searches.delete(search.id); - resolve(({ results: values(search.matches), stats: undefined })); - }, err => { - this._searches.delete(search.id); - reject(err); - }); - }, () => { - if (outer) { - outer.cancel(); - } - }); - } - - handleFindMatch(session: number, dataOrUri: UriComponents | [UriComponents, ILineMatch]): void { - if (!this._searches.has(session)) { - // ignore... - return; - } - let resource: URI; - let match: ILineMatch; - - if (Array.isArray(dataOrUri)) { - resource = URI.revive(dataOrUri[0]); - match = dataOrUri[1]; - } else { - resource = URI.revive(dataOrUri); - } - - this._searches.get(session).addMatch(resource, match); - } -} diff --git a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts new file mode 100644 index 00000000000..e83864d3a4a --- /dev/null +++ b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts @@ -0,0 +1,148 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { isFalsyOrEmpty } from 'vs/base/common/arrays'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { values } from 'vs/base/common/map'; +import URI, { UriComponents } from 'vs/base/common/uri'; +import { PPromise, TPromise } from 'vs/base/common/winjs.base'; +import { IFileMatch, ILineMatch, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, QueryType } from 'vs/platform/search/common/search'; +import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../node/extHost.protocol'; + +@extHostNamedCustomer(MainContext.MainThreadSearch) +export class MainThreadSearch implements MainThreadSearchShape { + + private readonly _proxy: ExtHostSearchShape; + private readonly _searchProvider = new Map(); + + constructor( + extHostContext: IExtHostContext, + @ISearchService private readonly _searchService: ISearchService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSearch); + } + + dispose(): void { + this._searchProvider.forEach(value => dispose()); + this._searchProvider.clear(); + } + + $registerSearchProvider(handle: number, scheme: string): void { + this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, scheme, handle, this._proxy)); + } + + $unregisterProvider(handle: number): void { + dispose(this._searchProvider.get(handle)); + this._searchProvider.delete(handle); + } + + $handleFindMatch(handle: number, session, data: UriComponents | [UriComponents, ILineMatch]): void { + this._searchProvider.get(handle).handleFindMatch(session, data); + } +} + +class SearchOperation { + + private static _idPool = 0; + + constructor( + readonly progress: (match: IFileMatch) => any, + readonly id: number = ++SearchOperation._idPool, + readonly matches = new Map() + ) { + // + } + + addMatch(resource: URI, match: ILineMatch): void { + if (!this.matches.has(resource.toString())) { + this.matches.set(resource.toString(), { resource, lineMatches: [] }); + } + if (match) { + this.matches.get(resource.toString()).lineMatches.push(match); + } + this.progress(this.matches.get(resource.toString())); + } +} + +class RemoteSearchProvider implements ISearchResultProvider { + + private readonly _registrations: IDisposable[]; + private readonly _searches = new Map(); + + + constructor( + searchService: ISearchService, + private readonly _scheme: string, + private readonly _handle: number, + private readonly _proxy: ExtHostSearchShape + ) { + this._registrations = [searchService.registerSearchResultProvider(this)]; + } + + dispose(): void { + dispose(this._registrations); + } + + search(query: ISearchQuery): PPromise { + + if (isFalsyOrEmpty(query.folderQueries)) { + return PPromise.as(undefined); + } + + let includes = { ...query.includePattern }; + let excludes = { ...query.excludePattern }; + + for (const folderQuery of query.folderQueries) { + if (folderQuery.folder.scheme === this._scheme) { + includes = { ...includes, ...folderQuery.includePattern }; + excludes = { ...excludes, ...folderQuery.excludePattern }; + } + } + + let outer: TPromise; + + return new PPromise((resolve, reject, report) => { + + const search = new SearchOperation(report); + this._searches.set(search.id, search); + + outer = query.type === QueryType.File + ? this._proxy.$provideFileSearchResults(this._handle, search.id, query.filePattern) + : this._proxy.$provideTextSearchResults(this._handle, search.id, query.contentPattern, { excludes: Object.keys(excludes), includes: Object.keys(includes) }); + + outer.then(() => { + this._searches.delete(search.id); + resolve(({ results: values(search.matches), stats: undefined })); + }, err => { + this._searches.delete(search.id); + reject(err); + }); + }, () => { + if (outer) { + outer.cancel(); + } + }); + } + + handleFindMatch(session: number, dataOrUri: UriComponents | [UriComponents, ILineMatch]): void { + if (!this._searches.has(session)) { + // ignore... + return; + } + let resource: URI; + let match: ILineMatch; + + if (Array.isArray(dataOrUri)) { + resource = URI.revive(dataOrUri[0]); + match = dataOrUri[1]; + } else { + resource = URI.revive(dataOrUri); + } + + this._searches.get(session).addMatch(resource, match); + } +} diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index ee9475f99b7..4e4583d1586 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -59,6 +59,7 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview'; import * as files from 'vs/platform/files/common/files'; +import { ExtHostSearch } from './extHostSearch'; export interface IExtensionApiFactory { (extension: IExtensionDescription): typeof vscode; @@ -116,6 +117,7 @@ export function createApiFactory( const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); + const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol)); const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); @@ -568,7 +570,7 @@ export function createApiFactory( return extHostFileSystem.registerFileSystemProvider(scheme, provider, newProvider); }), registerSearchProvider: proposedApiFunction(extension, (scheme, provider) => { - return extHostFileSystem.registerSearchProvider(scheme, provider); + return extHostSearch.registerSearchProvider(scheme, provider); }) }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 2301dee43bc..49e3cdb0b56 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -383,11 +383,13 @@ export interface IFileChangeDto { export interface MainThreadFileSystemShape extends IDisposable { $registerFileSystemProvider(handle: number, scheme: string): void; + $unregisterProvider(handle: number): void; + $onFileSystemChange(handle: number, resource: IFileChangeDto[]): void; +} + +export interface MainThreadSearchShape extends IDisposable { $registerSearchProvider(handle: number, scheme: string): void; $unregisterProvider(handle: number): void; - - $onFileSystemChange(handle: number, resource: IFileChangeDto[]): void; - $handleFindMatch(handle: number, session, data: UriComponents | [UriComponents, ILineMatch]): void; } @@ -576,7 +578,9 @@ export interface ExtHostFileSystemShape { $readdir(handle: number, resource: UriComponents): TPromise<[string, IStat][]>; $delete(handle: number, resource: UriComponents): TPromise; +} +export interface ExtHostSearchShape { $provideFileSearchResults(handle: number, session: number, query: string): TPromise; $provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, options: { includes: string[], excludes: string[] }): TPromise; } @@ -857,6 +861,7 @@ export const MainContext = { MainThreadFileSystem: createMainId('MainThreadFileSystem'), MainThreadExtensionService: createMainId('MainThreadExtensionService'), MainThreadSCM: createMainId('MainThreadSCM'), + MainThreadSearch: createMainId('MainThreadSearch'), MainThreadTask: createMainId('MainThreadTask'), MainThreadWindow: createMainId('MainThreadWindow'), }; @@ -882,6 +887,7 @@ export const ExtHostContext = { ExtHostLogService: createExtId('ExtHostLogService'), ExtHostTerminalService: createExtId('ExtHostTerminalService'), ExtHostSCM: createExtId('ExtHostSCM'), + ExtHostSearch: createExtId('ExtHostSearch'), ExtHostTask: createExtId('ExtHostTask'), ExtHostWorkspace: createExtId('ExtHostWorkspace'), ExtHostWindow: createExtId('ExtHostWindow'), diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index 965c01793e6..e6114f770a4 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -13,7 +13,6 @@ import * as files from 'vs/platform/files/common/files'; import * as path from 'path'; import { IDisposable } from 'vs/base/common/lifecycle'; import { asWinJsPromise } from 'vs/base/common/async'; -import { IPatternInfo } from 'vs/platform/search/common/search'; import { values } from 'vs/base/common/map'; import { Range, FileType, FileChangeType, FileChangeType2, FileType2 } from 'vs/workbench/api/node/extHostTypes'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; @@ -157,7 +156,6 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { private readonly _proxy: MainThreadFileSystemShape; private readonly _fsProvider = new Map(); - private readonly _searchProvider = new Map(); private readonly _linkProvider = new FsLinkProvider(); private _handlePool: number = 0; @@ -216,18 +214,6 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { }; } - registerSearchProvider(scheme: string, provider: vscode.SearchProvider) { - const handle = this._handlePool++; - this._searchProvider.set(handle, provider); - this._proxy.$registerSearchProvider(handle, scheme); - return { - dispose: () => { - this._searchProvider.delete(handle); - this._proxy.$unregisterProvider(handle); - } - }; - } - $stat(handle: number, resource: UriComponents): TPromise { return asWinJsPromise(token => this._fsProvider.get(handle).stat(URI.revive(resource), token)); } @@ -253,33 +239,4 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { $mkdir(handle: number, resource: UriComponents): TPromise { return asWinJsPromise(token => this._fsProvider.get(handle).createDirectory(URI.revive(resource), token)); } - - $provideFileSearchResults(handle: number, session: number, query: string): TPromise { - const provider = this._searchProvider.get(handle); - if (!provider.provideFileSearchResults) { - return TPromise.as(undefined); - } - const progress = { - report: (uri) => { - this._proxy.$handleFindMatch(handle, session, uri); - } - }; - return asWinJsPromise(token => provider.provideFileSearchResults(query, progress, token)); - } - $provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, options: { includes: string[], excludes: string[] }): TPromise { - const provider = this._searchProvider.get(handle); - if (!provider.provideTextSearchResults) { - return TPromise.as(undefined); - } - const progress = { - report: (data: vscode.TextSearchResult) => { - this._proxy.$handleFindMatch(handle, session, [data.uri, { - lineNumber: data.range.start.line, - preview: data.preview.leading + data.preview.matching + data.preview.trailing, - offsetAndLengths: [[data.preview.leading.length, data.preview.matching.length]] - }]); - } - }; - return asWinJsPromise(token => provider.provideTextSearchResults(pattern, options, progress, token)); - } } diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts new file mode 100644 index 00000000000..714e9bfe766 --- /dev/null +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { asWinJsPromise } from 'vs/base/common/async'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IPatternInfo } from 'vs/platform/search/common/search'; +import * as vscode from 'vscode'; +import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol'; + +export class ExtHostSearch implements ExtHostSearchShape { + + private readonly _proxy: MainThreadSearchShape; + private readonly _searchProvider = new Map(); + private _handlePool: number = 0; + + constructor(mainContext: IMainContext) { + this._proxy = mainContext.getProxy(MainContext.MainThreadSearch); + } + + registerSearchProvider(scheme: string, provider: vscode.SearchProvider) { + const handle = this._handlePool++; + this._searchProvider.set(handle, provider); + this._proxy.$registerSearchProvider(handle, scheme); + return { + dispose: () => { + this._searchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + } + }; + } + + $provideFileSearchResults(handle: number, session: number, query: string): TPromise { + const provider = this._searchProvider.get(handle); + if (!provider.provideFileSearchResults) { + return TPromise.as(undefined); + } + const progress = { + report: (uri) => { + this._proxy.$handleFindMatch(handle, session, uri); + } + }; + return asWinJsPromise(token => provider.provideFileSearchResults(query, progress, token)); + } + + $provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, options: { includes: string[], excludes: string[] }): TPromise { + const provider = this._searchProvider.get(handle); + if (!provider.provideTextSearchResults) { + return TPromise.as(undefined); + } + const progress = { + report: (data: vscode.TextSearchResult) => { + this._proxy.$handleFindMatch(handle, session, [data.uri, { + lineNumber: data.range.start.line, + preview: data.preview.leading + data.preview.matching + data.preview.trailing, + offsetAndLengths: [[data.preview.leading.length, data.preview.matching.length]] + }]); + } + }; + return asWinJsPromise(token => provider.provideTextSearchResults(pattern, options, progress, token)); + } +}