/*--------------------------------------------------------------------------------------------- * 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 { PPromise, TPromise } from 'vs/base/common/winjs.base'; import uri from 'vs/base/common/uri'; import objects = require('vs/base/common/objects'); import scorer = require('vs/base/common/scorer'); import strings = require('vs/base/common/strings'); import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; import { IProgress, LineMatch, FileMatch, ISearchComplete, ISearchProgressItem, QueryType, IFileMatch, ISearchQuery, ISearchConfiguration, ISearchService } from 'vs/platform/search/common/search'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IRawSearch, IFolderSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService } from './search'; import { ISearchChannel, SearchChannelClient } from './searchIpc'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ResourceMap } from 'vs/base/common/map'; export class SearchService implements ISearchService { public _serviceBrand: any; private diskSearch: DiskSearch; constructor( @IModelService private modelService: IModelService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IEnvironmentService environmentService: IEnvironmentService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IConfigurationService private configurationService: IConfigurationService ) { this.diskSearch = new DiskSearch(!environmentService.isBuilt || environmentService.verbose); } public extendQuery(query: ISearchQuery): void { const configuration = this.configurationService.getConfiguration(); // Configuration: Encoding if (!query.fileEncoding) { const fileEncoding = configuration && configuration.files && configuration.files.encoding; query.fileEncoding = fileEncoding; } // Configuration: File Excludes if (!query.disregardExcludeSettings) { const fileExcludes = 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 { this.extendQuery(query); let rawSearchQuery: PPromise; return new PPromise((onComplete, onError, onProgress) => { const searchP = this.diskSearch.search(query); // 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)); rawSearchQuery = searchP.then( // on Complete (complete) => { onComplete({ limitHit: complete.limitHit, results: complete.results.filter((match) => !localResults.has(match.resource)), // dont override local results stats: complete.stats }); }, // on Error (error) => { onError(error); }, // on Progress (progress) => { // Match if (progress.resource) { if (!localResults.has(progress.resource)) { // don't override local results onProgress(progress); } } // Progress else { onProgress(progress); } }); }, () => rawSearchQuery && rawSearchQuery.cancel()); } private getLocalResults(query: ISearchQuery): ResourceMap { const localResults = new ResourceMap(); 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 === 'untitled') { if (!this.untitledEditorService.exists(resource)) { return; } } // Don't support other resource schemes than files for now else if (resource.scheme !== '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 !== 'file') { return false; // if we match on file pattern, we have to ignore non file resources } if (!scorer.matches(resource.fsPath, strings.stripWildcards(query.filePattern).toLowerCase())) { return false; } } // includes if (query.includePattern) { if (resource.scheme !== 'file') { return false; // if we match on file patterns, we have to ignore non file resources } } return true; } public clearCache(cacheKey: string): TPromise { return this.diskSearch.clearCache(cacheKey); } } export class DiskSearch { private raw: IRawSearchService; constructor(verboseLogging: boolean, timeout: number = 60 * 60 * 1000) { const client = new Client( uri.parse(require.toUrl('bootstrap')).fsPath, { 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 `--debug-brk=xxx` and then forking a process from the extension host // results in the forked process inheriting `--debug-brk=xxx`. freshExecArgv: true, env: { AMD_ENTRYPOINT: 'vs/workbench/services/search/node/searchApp', PIPE_LOGGING: 'true', VERBOSE_LOGGING: verboseLogging } } ); const channel = getNextTickChannel(client.getChannel('search')); this.raw = new SearchChannelClient(channel); } public search(query: ISearchQuery): PPromise { let request: PPromise; let rawSearch: IRawSearch = { folderQueries: query.folderQueries ? query.folderQueries.map(q => { return { excludePattern: q.excludePattern, includePattern: q.includePattern, fileEncoding: q.fileEncoding, folder: q.folder.fsPath }; }) : [], extraFiles: query.extraFileResources ? query.extraFileResources.map(r => r.fsPath) : [], filePattern: query.filePattern, excludePattern: query.excludePattern, includePattern: query.includePattern, maxResults: query.maxResults, sortByScore: query.sortByScore, cacheKey: query.cacheKey, useRipgrep: query.useRipgrep, disregardIgnoreFiles: query.disregardIgnoreFiles }; if (query.type === QueryType.Text) { rawSearch.contentPattern = query.contentPattern; } if (query.type === QueryType.File) { request = this.raw.fileSearch(rawSearch); } else { request = this.raw.textSearch(rawSearch); } return DiskSearch.collectResults(request); } public static collectResults(request: PPromise): PPromise { let result: IFileMatch[] = []; return new PPromise((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 ((data).path) { const fileMatch = this.createFileMatch(data); result.push(fileMatch); p(fileMatch); } // Progress else { p(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 { return this.raw.clearCache(cacheKey); } }