Files
azuredatastudio/src/vs/workbench/services/search/node/searchService.ts
Karl Burtram dafb780987 Merge VS Code 1.21 source code (#1067)
* Initial VS Code 1.21 file copy with patches

* A few more merges

* Post npm install

* Fix batch of build breaks

* Fix more build breaks

* Fix more build errors

* Fix more build breaks

* Runtime fixes 1

* Get connection dialog working with some todos

* Fix a few packaging issues

* Copy several node_modules to package build to fix loader issues

* Fix breaks from master

* A few more fixes

* Make tests pass

* First pass of license header updates

* Second pass of license header updates

* Fix restore dialog issues

* Remove add additional themes menu items

* fix select box issues where the list doesn't show up

* formatting

* Fix editor dispose issue

* Copy over node modules to correct location on all platforms
2018-04-04 15:27:51 -07:00

381 lines
12 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { PPromise, TPromise } from 'vs/base/common/winjs.base';
import uri from 'vs/base/common/uri';
import objects = require('vs/base/common/objects');
import strings = require('vs/base/common/strings');
import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp';
import { IProgress, LineMatch, FileMatch, ISearchComplete, ISearchProgressItem, QueryType, IFileMatch, ISearchQuery, IFolderQuery, ISearchConfiguration, ISearchService, pathIncludedInQuery, ISearchResultProvider } from 'vs/platform/search/common/search';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService, ITelemetryEvent } from './search';
import { ISearchChannel, SearchChannelClient } from './searchIpc';
import { IEnvironmentService, IDebugParams } from 'vs/platform/environment/common/environment';
import { ResourceMap } from 'vs/base/common/map';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Schemas } from 'vs/base/common/network';
import * as pfs from 'vs/base/node/pfs';
import { ILogService } from 'vs/platform/log/common/log';
export class SearchService implements ISearchService {
public _serviceBrand: any;
private diskSearch: DiskSearch;
private readonly searchProvider: ISearchResultProvider[] = [];
private forwardingTelemetry: PPromise<void, ITelemetryEvent>;
constructor(
@IModelService private modelService: IModelService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IEnvironmentService environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
@IConfigurationService private configurationService: IConfigurationService,
@ILogService private logService: ILogService
) {
this.diskSearch = new DiskSearch(!environmentService.isBuilt || environmentService.verbose, /*timeout=*/undefined, environmentService.debugSearch);
this.registerSearchResultProvider(this.diskSearch);
}
public registerSearchResultProvider(provider: ISearchResultProvider): IDisposable {
this.searchProvider.push(provider);
return {
dispose: () => {
const idx = this.searchProvider.indexOf(provider);
if (idx >= 0) {
this.searchProvider.splice(idx, 1);
}
}
};
}
public extendQuery(query: ISearchQuery): void {
const configuration = this.configurationService.getValue<ISearchConfiguration>();
// Configuration: Encoding
if (!query.fileEncoding) {
const fileEncoding = configuration && configuration.files && configuration.files.encoding;
query.fileEncoding = fileEncoding;
}
// Configuration: File Excludes
if (!query.disregardExcludeSettings) {
const fileExcludes = objects.deepClone(configuration && configuration.files && configuration.files.exclude);
if (fileExcludes) {
if (!query.excludePattern) {
query.excludePattern = fileExcludes;
} else {
objects.mixin(query.excludePattern, fileExcludes, false /* no overwrite */);
}
}
}
}
public search(query: ISearchQuery): PPromise<ISearchComplete, ISearchProgressItem> {
this.forwardTelemetry();
let combinedPromise: TPromise<void>;
return new PPromise<ISearchComplete, ISearchProgressItem>((onComplete, onError, onProgress) => {
// Get local results from dirty/untitled
const localResults = this.getLocalResults(query);
// Allow caller to register progress callback
process.nextTick(() => localResults.values().filter((res) => !!res).forEach(onProgress));
this.logService.trace('SearchService#search', JSON.stringify(query));
const providerPromises = this.searchProvider.map(provider => TPromise.wrap(provider.search(query)).then(e => e,
err => {
// TODO@joh
// single provider fail. fail all?
onError(err);
},
progress => {
if (progress.resource) {
// Match
if (!localResults.has(progress.resource)) { // don't override local results
onProgress(progress);
}
} else {
// Progress
onProgress(<IProgress>progress);
}
if (progress.message) {
this.logService.debug('SearchService#search', progress.message);
}
}
));
combinedPromise = TPromise.join(providerPromises).then(values => {
const result: ISearchComplete = {
limitHit: false,
results: [],
stats: undefined
};
// TODO@joh
// sorting, disjunct results
for (const value of values) {
if (!value) {
continue;
}
// TODO@joh individual stats/limit
result.stats = value.stats || result.stats;
result.limitHit = value.limitHit || result.limitHit;
for (const match of value.results) {
if (!localResults.has(match.resource)) {
result.results.push(match);
}
}
}
return result;
}).then(onComplete, onError);
}, () => combinedPromise && combinedPromise.cancel());
}
private getLocalResults(query: ISearchQuery): ResourceMap<IFileMatch> {
const localResults = new ResourceMap<IFileMatch>();
if (query.type === QueryType.Text) {
let models = this.modelService.getModels();
models.forEach((model) => {
let resource = model.uri;
if (!resource) {
return;
}
// Support untitled files
if (resource.scheme === Schemas.untitled) {
if (!this.untitledEditorService.exists(resource)) {
return;
}
}
// Don't support other resource schemes than files for now
// todo@remote
// why is that? we should search for resources from other
// schemes
else if (resource.scheme !== Schemas.file) {
return;
}
if (!this.matches(resource, query)) {
return; // respect user filters
}
// Use editor API to find matches
let matches = model.findMatches(query.contentPattern.pattern, false, query.contentPattern.isRegExp, query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators : null, false, query.maxResults);
if (matches.length) {
let fileMatch = new FileMatch(resource);
localResults.set(resource, fileMatch);
matches.forEach((match) => {
fileMatch.lineMatches.push(new LineMatch(model.getLineContent(match.range.startLineNumber), match.range.startLineNumber - 1, [[match.range.startColumn - 1, match.range.endColumn - match.range.startColumn]]));
});
} else {
localResults.set(resource, null);
}
});
}
return localResults;
}
private matches(resource: uri, query: ISearchQuery): boolean {
// file pattern
if (query.filePattern) {
if (resource.scheme !== Schemas.file) {
return false; // if we match on file pattern, we have to ignore non file resources
}
if (!strings.fuzzyContains(resource.fsPath, strings.stripWildcards(query.filePattern).toLowerCase())) {
return false;
}
}
// includes
if (query.includePattern) {
if (resource.scheme !== Schemas.file) {
return false; // if we match on file patterns, we have to ignore non file resources
}
}
return pathIncludedInQuery(query, resource.fsPath);
}
public clearCache(cacheKey: string): TPromise<void> {
return this.diskSearch.clearCache(cacheKey);
}
private forwardTelemetry() {
if (!this.forwardingTelemetry) {
this.forwardingTelemetry = this.diskSearch.fetchTelemetry()
.then(null, onUnexpectedError, event => {
this.telemetryService.publicLog(event.eventName, event.data);
});
}
}
}
export class DiskSearch implements ISearchResultProvider {
private raw: IRawSearchService;
constructor(verboseLogging: boolean, timeout: number = 60 * 60 * 1000, searchDebug?: IDebugParams) {
const opts: IIPCOptions = {
serverName: 'Search',
timeout: timeout,
args: ['--type=searchService'],
// See https://github.com/Microsoft/vscode/issues/27665
// Pass in fresh execArgv to the forked process such that it doesn't inherit them from `process.execArgv`.
// e.g. Launching the extension host process with `--inspect-brk=xxx` and then forking a process from the extension host
// results in the forked process inheriting `--inspect-brk=xxx`.
freshExecArgv: true,
env: {
AMD_ENTRYPOINT: 'vs/workbench/services/search/node/searchApp',
PIPE_LOGGING: 'true',
VERBOSE_LOGGING: verboseLogging
}
};
if (searchDebug) {
if (searchDebug.break && searchDebug.port) {
opts.debugBrk = searchDebug.port;
} else if (!searchDebug.break && searchDebug.port) {
opts.debug = searchDebug.port;
}
}
const client = new Client(
uri.parse(require.toUrl('bootstrap')).fsPath,
opts);
const channel = getNextTickChannel(client.getChannel<ISearchChannel>('search'));
this.raw = new SearchChannelClient(channel);
}
public search(query: ISearchQuery): PPromise<ISearchComplete, ISearchProgressItem> {
const folderQueries = query.folderQueries || [];
return TPromise.join(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath)))
.then(exists => {
const existingFolders = folderQueries.filter((q, index) => exists[index]);
const rawSearch = this.rawSearchQuery(query, existingFolders);
let request: PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>;
if (query.type === QueryType.File) {
request = this.raw.fileSearch(rawSearch);
} else {
request = this.raw.textSearch(rawSearch);
}
return DiskSearch.collectResults(request);
});
}
private rawSearchQuery(query: ISearchQuery, existingFolders: IFolderQuery[]) {
let rawSearch: IRawSearch = {
folderQueries: [],
extraFiles: [],
filePattern: query.filePattern,
excludePattern: query.excludePattern,
includePattern: query.includePattern,
maxResults: query.maxResults,
exists: query.exists,
sortByScore: query.sortByScore,
cacheKey: query.cacheKey,
useRipgrep: query.useRipgrep,
disregardIgnoreFiles: query.disregardIgnoreFiles,
ignoreSymlinks: query.ignoreSymlinks
};
for (const q of existingFolders) {
rawSearch.folderQueries.push({
excludePattern: q.excludePattern,
includePattern: q.includePattern,
fileEncoding: q.fileEncoding,
disregardIgnoreFiles: q.disregardIgnoreFiles,
folder: q.folder.fsPath
});
}
if (query.extraFileResources) {
for (const r of query.extraFileResources) {
if (r.scheme === Schemas.file) {
rawSearch.extraFiles.push(r.fsPath);
}
}
}
if (query.type === QueryType.Text) {
rawSearch.contentPattern = query.contentPattern;
}
return rawSearch;
}
public static collectResults(request: PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>): PPromise<ISearchComplete, ISearchProgressItem> {
let result: IFileMatch[] = [];
return new PPromise<ISearchComplete, ISearchProgressItem>((c, e, p) => {
request.done((complete) => {
c({
limitHit: complete.limitHit,
results: result,
stats: complete.stats
});
}, e, (data) => {
// Matches
if (Array.isArray(data)) {
const fileMatches = data.map(d => this.createFileMatch(d));
result = result.concat(fileMatches);
fileMatches.forEach(p);
}
// Match
else if ((<ISerializedFileMatch>data).path) {
const fileMatch = this.createFileMatch(<ISerializedFileMatch>data);
result.push(fileMatch);
p(fileMatch);
}
// Progress
else {
p(<IProgress>data);
}
});
}, () => request.cancel());
}
private static createFileMatch(data: ISerializedFileMatch): FileMatch {
let fileMatch = new FileMatch(uri.file(data.path));
if (data.lineMatches) {
for (let j = 0; j < data.lineMatches.length; j++) {
fileMatch.lineMatches.push(new LineMatch(data.lineMatches[j].preview, data.lineMatches[j].lineNumber, data.lineMatches[j].offsetAndLengths));
}
}
return fileMatch;
}
public clearCache(cacheKey: string): TPromise<void> {
return this.raw.clearCache(cacheKey);
}
public fetchTelemetry(): PPromise<void, ITelemetryEvent> {
return this.raw.fetchTelemetry();
}
}