diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index 39029dc6706ac307da02ffe575cf175a2559dcc0..605eebab8a34904fab47084b88485d5092cd63de 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -317,7 +317,6 @@ export class OneLineRange extends SearchRange { export interface ISearchConfigurationProperties { exclude: glob.IExpression; useRipgrep: boolean; - useLegacySearch: boolean; /** * Use ignore file for file search. */ diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index bc43a26e383254718e1466eac933914d2c90b4a1..f5077e3bb16c572d5f5a3c129f3ff5e69ba76c34 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -36,32 +36,32 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { TreeResourceNavigator2, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ISearchHistoryService, ISearchHistoryValues, ISearchProgressItem, ITextQuery, SearchErrorCode, VIEW_ID, IProgress } from 'vs/platform/search/common/search'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ISearchHistoryService, ISearchHistoryValues, ITextQuery, SearchErrorCode, VIEW_ID } from 'vs/platform/search/common/search'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { ResourceLabels } from 'vs/workbench/browser/labels'; import { Viewlet } from 'vs/workbench/browser/viewlet'; import { IEditor } from 'vs/workbench/common/editor'; import { IPanel } from 'vs/workbench/common/panel'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/parts/search/browser/patternInputWidget'; -import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, getKeyboardEventForEditorOpen } from 'vs/workbench/parts/search/browser/searchActions'; -import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchDelegate, SearchAccessibilityProvider } from 'vs/workbench/parts/search/browser/searchResultsView'; +import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, getKeyboardEventForEditorOpen, RefreshAction } from 'vs/workbench/parts/search/browser/searchActions'; +import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate } from 'vs/workbench/parts/search/browser/searchResultsView'; import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/parts/search/browser/searchWidget'; import * as Constants from 'vs/workbench/parts/search/common/constants'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search'; -import { FileMatch, FileMatchOrMatch, FolderMatch, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, SearchModel, SearchResult, searchMatchComparer } from 'vs/workbench/parts/search/common/searchModel'; +import { FileMatch, FileMatchOrMatch, FolderMatch, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/parts/search/common/searchModel'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { ResourceLabels } from 'vs/workbench/browser/labels'; const $ = dom.$; @@ -1241,13 +1241,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): Thenable { - // Progress total is 100.0% for more progress bar granularity - const progressTotal = 1000; - let progressWorked = 0; - - const progressRunner = query.useRipgrep ? - this.progressService.show(/*infinite=*/true) : - this.progressService.show(progressTotal); + const progressRunner = this.progressService.show(/*infinite=*/true); this.searchWidget.searchInput.clearMessage(); this.searching = true; @@ -1263,12 +1257,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.searching = false; // Complete up to 100% as needed - if (completed && !query.useRipgrep) { - progressRunner.worked(progressTotal - progressWorked); - setTimeout(() => progressRunner.done(), 200); - } else { - progressRunner.done(); - } + progressRunner.done(); // Do final render, then expand if just 1 file with less than 50 matches this.onSearchResultsChanged(); @@ -1370,13 +1359,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR }); this.viewModel.searchResult.clear(); - if (e.code === SearchErrorCode.unknownEncoding && !this.configurationService.getValue('search.useLegacySearch')) { - this.notificationService.prompt(Severity.Info, nls.localize('otherEncodingWarning', "You can enable \"search.useLegacySearch\" to search non-standard file encodings."), - [{ - label: nls.localize('otherEncodingWarning.openSettingsLabel', "Open Settings"), - run: () => this.openSettings('search.useLegacySearch') - }]); - } else if (e.code === SearchErrorCode.regexParseError && !this.configurationService.getValue('search.usePCRE2')) { + if (e.code === SearchErrorCode.regexParseError && !this.configurationService.getValue('search.usePCRE2')) { this.showPcre2Hint(); } @@ -1384,18 +1367,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } }; - let total: number = 0; - let worked: number = 0; let visibleMatches = 0; - const onProgress = (p: ISearchProgressItem) => { - // Progress - if ((p).total) { - total = (p).total; - } - if ((p).worked) { - worked = (p).worked; - } - }; // Handle UI updates in an interval to show frequent progress and results const uiRefreshHandle: any = setInterval(() => { @@ -1404,30 +1376,6 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { return; } - if (!query.useRipgrep) { - // Progress bar update - let fakeProgress = true; - if (total > 0 && worked > 0) { - const ratio = Math.round((worked / total) * progressTotal); - if (ratio > progressWorked) { // never show less progress than what we have already - progressRunner.worked(ratio - progressWorked); - progressWorked = ratio; - fakeProgress = false; - } - } - - // Fake progress up to 90%, or when actual progress beats it - const fakeMax = 900; - const fakeMultiplier = 12; - if (fakeProgress && progressWorked < fakeMax) { - // Linearly decrease the rate of fake progress. - // 1 is the smallest allowed amount of progress. - const fakeAmt = Math.round((fakeMax - progressWorked) / fakeMax * fakeMultiplier) || 1; - progressWorked += fakeAmt; - progressRunner.worked(fakeAmt); - } - } - // Search result tree update const fileCount = this.viewModel.searchResult.fileCount(); if (visibleMatches !== fileCount) { @@ -1441,7 +1389,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.searchWidget.setReplaceAllActionState(false); - return this.viewModel.search(query, onProgress) + return this.viewModel.search(query) .then(onComplete, onError); } diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index 979b7a2e4d98072b07d914326703fcfd91bc54d1..035215bddeb2222e4020b145734830b5f86b06b1 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -51,7 +51,6 @@ export interface ICommonQueryBuilderOptions { maxResults?: number; maxFileSize?: number; - useRipgrep?: boolean; disregardIgnoreFiles?: boolean; disregardGlobalIgnoreFiles?: boolean; disregardExcludeSettings?: boolean; @@ -150,11 +149,6 @@ export class QueryBuilder { folderResources.map(uri => this.getFolderQueryForRoot(uri, options, excludePattern))) .filter(query => !!query) as IFolderQuery[]; - // const useRipgrep = !folderResources || folderResources.every(folder => { - // const folderConfig = this.configurationService.getValue({ resource: folder }); - // return !folderConfig.search.useLegacySearch; - // }); - const queryProps: ICommonQueryProps = { _reason: options._reason, folderQueries, @@ -163,8 +157,7 @@ export class QueryBuilder { excludePattern: excludePattern.pattern, includePattern, - maxResults: options.maxResults, - useRipgrep: true + maxResults: options.maxResults }; // Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index f254431f6ded3f223065f0167f99d648595976db..edd87128a3f4ce5856ac0f360925a163440b55f6 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -960,7 +960,6 @@ export class SearchModel extends Disposable { "fileCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "options": { "${inline}": [ "${IPatternInfo}" ] }, "duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "useRipgrep": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "scheme" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } } @@ -970,7 +969,6 @@ export class SearchModel extends Disposable { fileCount: this._searchResult.fileCount(), options, duration, - useRipgrep: this._searchQuery.useRipgrep, type: stats && stats.type, scheme }); diff --git a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts index 67263d4f9541740fa14a71286ee2f522cbb7f76c..a299f18b82a8b889caebdedd0e8e7d7a2bcac820 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -618,11 +618,6 @@ configurationRegistry.registerConfiguration({ deprecationMessage: nls.localize('useRipgrepDeprecated', "Deprecated. Consider \"search.usePCRE2\" for advanced regex feature support."), default: true }, - 'search.useLegacySearch': { - type: 'boolean', - description: nls.localize('useLegacySearch', "Controls whether to use the deprecated legacy mode for text and file search. It supports some text encodings that are not supported by the standard ripgrep-based search."), - default: false - }, 'search.useIgnoreFiles': { type: 'boolean', markdownDescription: nls.localize('useIgnoreFiles', "Controls whether to use `.gitignore` and `.ignore` files when searching for files."), diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 138360c0cccb7b9abaaa7a706d38c19bf0c8a2dc..2fe094108c80ff4d628bcca5022bbb07cc68b13c 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -75,7 +75,7 @@ export class FileWalker { constructor(config: IFileQuery, maxFileSize?: number) { this.config = config; - this.useRipgrep = config.useRipgrep !== false; + this.useRipgrep = true; this.filePattern = config.filePattern || ''; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || null; diff --git a/src/vs/workbench/services/search/node/legacy/rawLegacyTextSearchService.ts b/src/vs/workbench/services/search/node/legacy/rawLegacyTextSearchService.ts deleted file mode 100644 index e7189c56c12c0909016be878f94952bb45572d1f..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/search/node/legacy/rawLegacyTextSearchService.ts +++ /dev/null @@ -1,75 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs'; -import * as gracefulFs from 'graceful-fs'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { MAX_FILE_SIZE } from 'vs/platform/files/node/files'; -import { ITextQuery, QueryType } from 'vs/platform/search/common/search'; -import { FileWalker } from 'vs/workbench/services/search/node/fileSearch'; -import { Engine } from 'vs/workbench/services/search/node/legacy/textSearch'; -import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/legacy/textSearchWorkerProvider'; -import { BatchedCollector } from 'vs/workbench/services/search/node/textSearchManager'; -import { ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from '../search'; - -gracefulFs.gracefulify(fs); - -type IProgressCallback = (p: ISerializedSearchProgressItem) => void; - -export class LegacyTextSearchService { - private static readonly BATCH_SIZE = 512; - - private textSearchWorkerProvider: TextSearchWorkerProvider; - - textSearch(config: ITextQuery, progressCallback: IProgressCallback, token?: CancellationToken): Promise { - if (!this.textSearchWorkerProvider) { - this.textSearchWorkerProvider = new TextSearchWorkerProvider(); - } - - let engine = new Engine( - config, - new FileWalker({ - type: QueryType.File, - folderQueries: config.folderQueries, - extraFileResources: config.extraFileResources, - includePattern: config.includePattern, - excludePattern: config.excludePattern, - useRipgrep: false, - filePattern: '' - }, MAX_FILE_SIZE), - this.textSearchWorkerProvider); - - return this.doTextSearch(engine, progressCallback, LegacyTextSearchService.BATCH_SIZE, token); - } - - private doTextSearch(engine: Engine, progressCallback: IProgressCallback, batchSize: number, token?: CancellationToken): Promise { - if (token) { - token.onCancellationRequested(() => engine.cancel()); - } - - return new Promise((c, e) => { - // Use BatchedCollector to get new results to the frontend every 2s at least, until 50 results have been returned - const collector = new BatchedCollector(batchSize, progressCallback); - engine.search((matches) => { - const totalMatches = matches.reduce((acc, m) => acc + m.numMatches!, 0); - collector.addItems(matches, totalMatches); - }, (progress) => { - progressCallback(progress); - }, (error, stats) => { - collector.flush(); - - if (error) { - e(error); - } else { - c({ - type: 'success', - limitHit: stats.limitHit, - stats: null - }); - } - }); - }); - } -} diff --git a/src/vs/workbench/services/search/node/legacy/search.ts b/src/vs/workbench/services/search/node/legacy/search.ts deleted file mode 100644 index 84cac9d4f5aa628967633795d61ebd8a4814ac38..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/search/node/legacy/search.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as glob from 'vs/base/common/glob'; -import { IPatternInfo, ITextSearchPreviewOptions } from 'vs/platform/search/common/search'; - -export interface IFolderSearch { - folder: string; - excludePattern?: glob.IExpression; - includePattern?: glob.IExpression; - fileEncoding?: string; - disregardIgnoreFiles?: boolean; - disregardGlobalIgnoreFiles?: boolean; -} - -export interface IRawSearch { - folderQueries: IFolderSearch[]; - ignoreSymlinks?: boolean; - extraFiles?: string[]; - filePattern?: string; - excludePattern?: glob.IExpression; - includePattern?: glob.IExpression; - contentPattern: IPatternInfo; - maxResults?: number; - exists?: boolean; - sortByScore?: boolean; - cacheKey?: string; - maxFilesize?: number; - useRipgrep?: boolean; - disregardIgnoreFiles?: boolean; - previewOptions?: ITextSearchPreviewOptions; - disregardGlobalIgnoreFiles?: boolean; -} diff --git a/src/vs/workbench/services/search/node/legacy/textSearch.ts b/src/vs/workbench/services/search/node/legacy/textSearch.ts deleted file mode 100644 index 0c8fa95a224933f8956eba03c44e93b9ad44f60f..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/search/node/legacy/textSearch.ts +++ /dev/null @@ -1,205 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as path from 'path'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IProgress, ITextQuery } from 'vs/platform/search/common/search'; -import { FileWalker } from 'vs/workbench/services/search/node/fileSearch'; -import { ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch } from '../search'; -import { ITextSearchWorkerProvider } from './textSearchWorkerProvider'; -import { ISearchWorker, ISearchWorkerSearchArgs } from './worker/searchWorkerIpc'; -import { IRawSearch } from 'vs/workbench/services/search/node/legacy/search'; - -export class Engine implements ISearchEngine { - - private static readonly PROGRESS_FLUSH_CHUNK_SIZE = 50; // optimization: number of files to process before emitting progress event - - private config: IRawSearch; - private config2: ITextQuery; - private walker: FileWalker; - private walkerError: Error | null; - - private isCanceled = false; - private isDone = false; - private totalBytes = 0; - private processedBytes = 0; - private progressed = 0; - private walkerIsDone = false; - private limitReached = false; - private numResults = 0; - - private workerProvider: ITextSearchWorkerProvider; - private workers: ISearchWorker[]; - - private nextWorker = 0; - - constructor(config: ITextQuery, walker: FileWalker, workerProvider: ITextSearchWorkerProvider) { - this.config = makeRawSearch(config); - this.config2 = config; - this.walker = walker; - this.workerProvider = workerProvider; - } - - cancel(): void { - this.isCanceled = true; - this.walker.cancel(); - - this.workers.forEach(w => { - w.cancel() - .then(undefined, onUnexpectedError); - }); - } - - initializeWorkers(): void { - this.workers.forEach(w => { - w.initialize() - .then(undefined, onUnexpectedError); - }); - } - - search(onResult: (match: ISerializedFileMatch[]) => void, onProgress: (progress: IProgress) => void, done: (error: Error | null, complete: ISearchEngineSuccess) => void): void { - this.workers = this.workerProvider.getWorkers(); - this.initializeWorkers(); - - const fileEncoding = this.config.folderQueries.length === 1 ? - this.config.folderQueries[0].fileEncoding || 'utf8' : - 'utf8'; - - const progress = () => { - if (++this.progressed % Engine.PROGRESS_FLUSH_CHUNK_SIZE === 0) { - onProgress({ total: this.totalBytes, worked: this.processedBytes }); // buffer progress in chunks to reduce pressure - } - }; - - const unwind = (processed: number) => { - this.processedBytes += processed; - - // Emit progress() unless we got canceled or hit the limit - if (processed && !this.isDone && !this.isCanceled && !this.limitReached) { - progress(); - } - - // Emit done() - if (!this.isDone && this.processedBytes === this.totalBytes && this.walkerIsDone) { - this.isDone = true; - done(this.walkerError, { - limitHit: this.limitReached, - stats: this.walker.getStats() - }); - } - }; - - const run = (batch: string[], batchBytes: number): void => { - const worker = this.workers[this.nextWorker]; - this.nextWorker = (this.nextWorker + 1) % this.workers.length; - - const maxResults = this.config.maxResults && (this.config.maxResults - this.numResults); - const searchArgs: ISearchWorkerSearchArgs = { absolutePaths: batch, maxResults, pattern: this.config.contentPattern, fileEncoding, previewOptions: this.config.previewOptions }; - worker.search(searchArgs).then(result => { - if (!result || this.limitReached || this.isCanceled) { - return unwind(batchBytes); - } - - const matches = result.matches; - onResult(matches); - this.numResults += result.numMatches; - - if (this.config.maxResults && this.numResults >= this.config.maxResults) { - // It's possible to go over maxResults like this, but it's much simpler than trying to extract the exact number - // of file matches, line matches, and matches within a line to == maxResults. - this.limitReached = true; - } - - unwind(batchBytes); - }, - error => { - // An error on the worker's end, not in reading the file, but in processing the batch. Log and continue. - onUnexpectedError(error); - unwind(batchBytes); - }); - }; - - // Walk over the file system - let nextBatch: string[] = []; - let nextBatchBytes = 0; - const batchFlushBytes = 2 ** 20; // 1MB - this.walker.walk(this.config2.folderQueries, this.config2.extraFileResources || [], result => { - let bytes = result.size || 1; - this.totalBytes += bytes; - - // If we have reached the limit or we are canceled, ignore it - if (this.limitReached || this.isCanceled) { - return unwind(bytes); - } - - // Indicate progress to the outside - progress(); - - const absolutePath = result.base ? [result.base, result.relativePath].join(path.sep) : result.relativePath; - nextBatch.push(absolutePath); - nextBatchBytes += bytes; - - if (nextBatchBytes >= batchFlushBytes) { - run(nextBatch, nextBatchBytes); - nextBatch = []; - nextBatchBytes = 0; - } - }, - onProgress, - (error, isLimitHit) => { - this.walkerIsDone = true; - this.walkerError = error; - - // Send any remaining paths to a worker, or unwind if we're stopping - if (nextBatch.length) { - if (this.limitReached || this.isCanceled) { - unwind(nextBatchBytes); - } else { - run(nextBatch, nextBatchBytes); - } - } else { - unwind(0); - } - }); - } -} - -/** - * Exported for tests - */ -export function makeRawSearch(query: ITextQuery): IRawSearch { - let rawSearch: IRawSearch = { - folderQueries: [], - extraFiles: [], - excludePattern: query.excludePattern, - includePattern: query.includePattern, - maxResults: query.maxResults, - useRipgrep: query.useRipgrep, - disregardIgnoreFiles: query.folderQueries.some(fq => fq.disregardIgnoreFiles!), - disregardGlobalIgnoreFiles: query.folderQueries.some(fq => fq.disregardGlobalIgnoreFiles!), - ignoreSymlinks: query.folderQueries.some(fq => fq.ignoreSymlinks!), - previewOptions: query.previewOptions, - contentPattern: query.contentPattern - }; - - for (const q of query.folderQueries) { - rawSearch.folderQueries.push({ - excludePattern: q.excludePattern, - includePattern: q.includePattern, - fileEncoding: q.fileEncoding, - disregardIgnoreFiles: q.disregardIgnoreFiles, - disregardGlobalIgnoreFiles: q.disregardGlobalIgnoreFiles, - folder: q.folder.fsPath - }); - } - - if (query.extraFileResources) { - for (const r of query.extraFileResources) { - rawSearch.extraFiles!.push(r.fsPath); - } - } - - return rawSearch; -} diff --git a/src/vs/workbench/services/search/node/legacy/textSearchWorkerProvider.ts b/src/vs/workbench/services/search/node/legacy/textSearchWorkerProvider.ts deleted file mode 100644 index ac848db101e09dc3df1dca11f87e01c09c785017..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/search/node/legacy/textSearchWorkerProvider.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as os from 'os'; - -import * as ipc from 'vs/base/parts/ipc/node/ipc'; -import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; - -import { ISearchWorker, SearchWorkerChannelClient } from './worker/searchWorkerIpc'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; - -export interface ITextSearchWorkerProvider { - getWorkers(): ISearchWorker[]; -} - -export class TextSearchWorkerProvider implements ITextSearchWorkerProvider { - private workers: ISearchWorker[] = []; - - getWorkers(): ISearchWorker[] { - const numWorkers = os.cpus().length; - while (this.workers.length < numWorkers) { - this.createWorker(); - } - - return this.workers; - } - - private createWorker(): void { - let client = new Client( - getPathFromAmdModule(require, 'bootstrap-fork'), - { - serverName: 'Search Worker ' + this.workers.length, - args: ['--type=searchWorker'], - timeout: 30 * 1000, - env: { - AMD_ENTRYPOINT: 'vs/workbench/services/search/node/legacy/worker/searchWorkerApp', - PIPE_LOGGING: 'true', - VERBOSE_LOGGING: process.env.VERBOSE_LOGGING - }, - useQueue: true - }); - - const channel = ipc.getNextTickChannel(client.getChannel('searchWorker')); - const channelClient = new SearchWorkerChannelClient(channel); - - this.workers.push(channelClient); - } -} diff --git a/src/vs/workbench/services/search/node/legacy/worker/searchWorker.ts b/src/vs/workbench/services/search/node/legacy/worker/searchWorker.ts deleted file mode 100644 index 084455e1e46be1f7f33281cff64a52ac01870c02..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/search/node/legacy/worker/searchWorker.ts +++ /dev/null @@ -1,291 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs'; -import * as gracefulFs from 'graceful-fs'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import * as strings from 'vs/base/common/strings'; -import { bomLength, decode, detectEncodingFromBuffer, encodingExists, UTF16be, UTF16le, UTF8, UTF8_with_bom } from 'vs/base/node/encoding'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextSearchPreviewOptions, TextSearchMatch } from 'vs/platform/search/common/search'; -import { ISearchWorker, ISearchWorkerSearchArgs, ISearchWorkerSearchResult } from './searchWorkerIpc'; -import { FileMatch } from 'vs/workbench/services/search/node/search'; - -gracefulFs.gracefulify(fs); - -interface ReadLinesOptions { - bufferLength: number; - encoding: string; -} - -const MAX_FILE_ERRORS = 5; // Don't report more than this number of errors, 1 per file, to avoid flooding the log when there's a general issue -let numErrorsLogged = 0; -function onError(error: any): void { - if (numErrorsLogged++ < MAX_FILE_ERRORS) { - onUnexpectedError(error); - } -} - -export class SearchWorker implements ISearchWorker { - private currentSearchEngine: SearchWorkerEngine; - - initialize(): Promise { - this.currentSearchEngine = new SearchWorkerEngine(); - return Promise.resolve(undefined); - } - - cancel(): Promise { - // Cancel the current search. It will stop searching and close its open files. - if (this.currentSearchEngine) { - this.currentSearchEngine.cancel(); - } - - return Promise.resolve(undefined); - } - - search(args: ISearchWorkerSearchArgs): Promise { - if (!this.currentSearchEngine) { - // Worker timed out during search - this.initialize(); - } - - return this.currentSearchEngine.searchBatch(args); - } -} - -interface IFileSearchResult { - match: FileMatch; - numMatches: number; - limitReached?: boolean; -} - -const LF = 0x0A; -const CR = 0x0D; - -export class SearchWorkerEngine { - private nextSearch: Promise = Promise.resolve(null); - private isCanceled = false; - - /** - * Searches some number of the given paths concurrently, and starts searches in other paths when those complete. - */ - searchBatch(args: ISearchWorkerSearchArgs): Promise { - const contentPattern = strings.createRegExp(args.pattern.pattern, !!args.pattern.isRegExp, { matchCase: args.pattern.isCaseSensitive, wholeWord: args.pattern.isWordMatch, multiline: false, global: true }); - const fileEncoding = encodingExists(args.fileEncoding) ? args.fileEncoding : UTF8; - return this.nextSearch = - this.nextSearch.then(() => this._searchBatch(args, contentPattern, fileEncoding)); - } - - - private _searchBatch(args: ISearchWorkerSearchArgs, contentPattern: RegExp, fileEncoding: string): Promise { - if (this.isCanceled) { - return Promise.resolve(null); - } - - return new Promise(batchDone => { - const result: ISearchWorkerSearchResult = { - matches: [], - numMatches: 0, - limitReached: false - }; - - // Search in the given path, and when it's finished, search in the next path in absolutePaths - const startSearchInFile = (absolutePath: string): Promise => { - return this.searchInFile(absolutePath, contentPattern, fileEncoding, args.maxResults && (args.maxResults - result.numMatches), args.previewOptions).then(fileResult => { - // Finish early if search is canceled - if (this.isCanceled) { - return; - } - - if (fileResult) { - result.numMatches += fileResult.numMatches; - result.matches.push(fileResult.match.serialize()); - if (fileResult.limitReached) { - // If the limit was reached, terminate early with the results so far and cancel in-progress searches. - this.cancel(); - result.limitReached = true; - return batchDone(result); - } - } - }, onError); - }; - - Promise.all(args.absolutePaths.map(startSearchInFile)).then(() => { - batchDone(result); - }); - }); - } - - cancel(): void { - this.isCanceled = true; - } - - private searchInFile(absolutePath: string, contentPattern: RegExp, fileEncoding: string, maxResults?: number, previewOptions?: ITextSearchPreviewOptions): Promise { - let fileMatch: FileMatch | null = null; - let limitReached = false; - let numMatches = 0; - - const perLineCallback = (line: string, lineNumber: number) => { - let match = contentPattern.exec(line); - - // Record all matches into file result - while (match !== null && match[0].length > 0 && !this.isCanceled && !limitReached) { - if (fileMatch === null) { - fileMatch = new FileMatch(absolutePath); - } - - const lineMatch = new TextSearchMatch(line, new Range(lineNumber, match.index, lineNumber, match.index + match[0].length), previewOptions); - fileMatch.addMatch(lineMatch); - - numMatches++; - if (maxResults && numMatches >= maxResults) { - limitReached = true; - } - - match = contentPattern.exec(line); - } - }; - - // Read lines buffered to support large files - return this.readlinesAsync(absolutePath, perLineCallback, { bufferLength: 8096, encoding: fileEncoding }).then( - () => fileMatch ? { match: fileMatch, limitReached, numMatches } : null); - } - - private readlinesAsync(filename: string, perLineCallback: (line: string, lineNumber: number) => void, options: ReadLinesOptions): Promise { - return new Promise((resolve, reject) => { - fs.open(filename, 'r', null, (error: Error, fd: number) => { - if (error) { - return resolve(undefined); - } - - const buffer = Buffer.allocUnsafe(options.bufferLength); - let line = ''; - let lineNumber = 0; - let lastBufferHadTrailingCR = false; - - const readFile = (isFirstRead: boolean, clb: (error: Error | null) => void): void => { - if (this.isCanceled) { - return clb(null); // return early if canceled or limit reached - } - - fs.read(fd, buffer, 0, buffer.length, null, (error: Error, bytesRead: number, buffer: Buffer) => { - const decodeBuffer = (buffer: Buffer, start: number, end: number): string => { - if (options.encoding === UTF8 || options.encoding === UTF8_with_bom) { - return buffer.toString(undefined, start, end); // much faster to use built in toString() when encoding is default - } - - return decode(buffer.slice(start, end), options.encoding); - }; - - const lineFinished = (offset: number): void => { - line += decodeBuffer(buffer, pos, i + offset); - perLineCallback(line, lineNumber); - line = ''; - lineNumber++; - pos = i + offset; - }; - - if (error || bytesRead === 0 || this.isCanceled) { - return clb(error); // return early if canceled or limit reached or no more bytes to read - } - - let crlfCharSize = 1; - let crBytes = [CR]; - let lfBytes = [LF]; - let pos = 0; - let i = 0; - - // Detect encoding and mime when this is the beginning of the file - if (isFirstRead) { - const detected = detectEncodingFromBuffer({ buffer, bytesRead }, false); - if (detected.seemsBinary) { - return clb(null); // skip files that seem binary - } - - // Check for BOM offset - switch (detected.encoding) { - case UTF8: - pos = i = bomLength(UTF8); - options.encoding = UTF8; - break; - case UTF16be: - pos = i = bomLength(UTF16be); - options.encoding = UTF16be; - break; - case UTF16le: - pos = i = bomLength(UTF16le); - options.encoding = UTF16le; - break; - } - - // when we are running with UTF16le/be, LF and CR are encoded as - // two bytes, like 0A 00 (LF) / 0D 00 (CR) for LE or flipped around - // for BE. We need to account for this when splitting the buffer into - // newlines, and when detecting a CRLF combo. - if (options.encoding === UTF16le) { - crlfCharSize = 2; - crBytes = [CR, 0x00]; - lfBytes = [LF, 0x00]; - } else if (options.encoding === UTF16be) { - crlfCharSize = 2; - crBytes = [0x00, CR]; - lfBytes = [0x00, LF]; - } - } - - if (lastBufferHadTrailingCR) { - if (buffer[i] === lfBytes[0] && (lfBytes.length === 1 || buffer[i + 1] === lfBytes[1])) { - lineFinished(1 * crlfCharSize); - i++; - } else { - lineFinished(0); - } - - lastBufferHadTrailingCR = false; - } - - /** - * This loop executes for every byte of every file in the workspace - it is highly performance-sensitive! - * Hence the duplication in reading the buffer to avoid a function call. Previously a function call was not - * being inlined by V8. - */ - for (; i < bytesRead; ++i) { - if (buffer[i] === lfBytes[0] && (lfBytes.length === 1 || buffer[i + 1] === lfBytes[1])) { - lineFinished(1 * crlfCharSize); - } else if (buffer[i] === crBytes[0] && (crBytes.length === 1 || buffer[i + 1] === crBytes[1])) { // CR (Carriage Return) - if (i + crlfCharSize === bytesRead) { - lastBufferHadTrailingCR = true; - } else if (buffer[i + crlfCharSize] === lfBytes[0] && (lfBytes.length === 1 || buffer[i + crlfCharSize + 1] === lfBytes[1])) { - lineFinished(2 * crlfCharSize); - i += 2 * crlfCharSize - 1; - } else { - lineFinished(1 * crlfCharSize); - } - } - } - - line += decodeBuffer(buffer, pos, bytesRead); - - readFile(/*isFirstRead=*/false, clb); // Continue reading - }); - }; - - readFile(/*isFirstRead=*/true, (error: Error) => { - if (error) { - return resolve(undefined); - } - - if (line.length) { - perLineCallback(line, lineNumber); // handle last line - } - - fs.close(fd, (error: Error) => { - resolve(undefined); - }); - }); - }); - }); - } -} diff --git a/src/vs/workbench/services/search/node/legacy/worker/searchWorkerApp.ts b/src/vs/workbench/services/search/node/legacy/worker/searchWorkerApp.ts deleted file mode 100644 index 3abda129ab48055031382a4f36ecf54c72aee5e3..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/search/node/legacy/worker/searchWorkerApp.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; -import { SearchWorkerChannel } from './searchWorkerIpc'; -import { SearchWorker } from './searchWorker'; - -const server = new Server('searchWorker'); -const worker = new SearchWorker(); -const channel = new SearchWorkerChannel(worker); -server.registerChannel('searchWorker', channel); diff --git a/src/vs/workbench/services/search/node/legacy/worker/searchWorkerIpc.ts b/src/vs/workbench/services/search/node/legacy/worker/searchWorkerIpc.ts deleted file mode 100644 index 693574a37940d1a842dfa4dc21d73af864cb2c11..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/search/node/legacy/worker/searchWorkerIpc.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IPatternInfo, ITextSearchPreviewOptions } from 'vs/platform/search/common/search'; -import { SearchWorker } from './searchWorker'; -import { Event } from 'vs/base/common/event'; -import { ISerializedFileMatch } from 'vs/workbench/services/search/node/search'; - -export interface ISearchWorkerSearchArgs { - pattern: IPatternInfo; - fileEncoding: string; - absolutePaths: string[]; - maxResults?: number; - previewOptions?: ITextSearchPreviewOptions; -} - -export interface ISearchWorkerSearchResult { - matches: ISerializedFileMatch[]; - numMatches: number; - limitReached: boolean; -} - -export interface ISearchWorker { - initialize(): Promise; - search(args: ISearchWorkerSearchArgs): Promise; - cancel(): Promise; -} - -export class SearchWorkerChannel implements IServerChannel { - constructor(private worker: SearchWorker) { - } - - listen(): Event { - throw new Error('No events'); - } - - call(_, command: string, arg?: any): Promise { - switch (command) { - case 'initialize': return this.worker.initialize(); - case 'search': return this.worker.search(arg); - case 'cancel': return this.worker.cancel(); - } - throw new Error(`Call not found: ${command}`); - } -} - -export class SearchWorkerChannelClient implements ISearchWorker { - constructor(private channel: IChannel) { } - - initialize(): Promise { - return this.channel.call('initialize'); - } - - search(args: ISearchWorkerSearchArgs): Promise { - return this.channel.call('search', args); - } - - cancel(): Promise { - return this.channel.call('cancel'); - } -} diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 4dc4c3bca14946c9affb01ed9898272196b71480..da52da42622e80f7068f1f770e2f13db2d8934c0 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -19,7 +19,6 @@ import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'v import { MAX_FILE_SIZE } from 'vs/platform/files/node/files'; import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery } from 'vs/platform/search/common/search'; import { Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; -import { LegacyTextSearchService } from 'vs/workbench/services/search/node/legacy/rawLegacyTextSearchService'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; import { IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from './search'; @@ -32,7 +31,6 @@ export class SearchService implements IRawSearchService { private static readonly BATCH_SIZE = 512; - private legacyTextSearchService = new LegacyTextSearchService(); private caches: { [cacheKey: string]: Cache; } = Object.create(null); fileSearch(config: IRawFileQuery): Event { @@ -64,9 +62,7 @@ export class SearchService implements IRawSearchService { const emitter = new Emitter({ onFirstListenerDidAdd: () => { promise = createCancelablePromise(token => { - return (rawQuery.useRipgrep ? - this.ripgrepTextSearch(query, p => emitter.fire(p), token) : - this.legacyTextSearchService.textSearch(query, p => emitter.fire(p), token)); + return this.ripgrepTextSearch(query, p => emitter.fire(p), token); }); promise.then( diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index 6d77611748465a9fd0f2fcabaaf5a4a004e7a661..8b56afda7216dca7b8be579cd135a49d851edff0 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -9,15 +9,10 @@ import { getPathFromAmdModule } from 'vs/base/common/amd'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as glob from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; -import { IFolderQuery, ISearchRange, ITextQuery, ITextSearchMatch, QueryType, ITextSearchContext, deserializeSearchError, SearchErrorCode } from 'vs/platform/search/common/search'; -import { LegacyTextSearchService } from 'vs/workbench/services/search/node/legacy/rawLegacyTextSearchService'; +import { deserializeSearchError, IFolderQuery, ISearchRange, ITextQuery, ITextSearchContext, ITextSearchMatch, QueryType, SearchErrorCode } from 'vs/platform/search/common/search'; import { ISerializedFileMatch } from 'vs/workbench/services/search/node/search'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; -function countAll(matches: ISerializedFileMatch[]): number { - return matches.reduce((acc, m) => acc + m.numMatches!, 0); -} - const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); const EXAMPLES_FIXTURES = path.join(TEST_FIXTURES, 'examples'); const MORE_FIXTURES = path.join(TEST_FIXTURES, 'more'); @@ -31,24 +26,7 @@ const MULTIROOT_QUERIES: IFolderQuery[] = [ { folder: URI.file(MORE_FIXTURES) } ]; -function doLegacySearchTest(config: ITextQuery, expectedResultCount: number | Function): Promise { - const engine = new LegacyTextSearchService(); - - let c = 0; - return engine.textSearch(config, (result) => { - if (result && Array.isArray(result)) { - c += countAll(result); - } - }, null!).then(() => { - if (typeof expectedResultCount === 'function') { - assert(expectedResultCount(c)); - } else { - assert.equal(c, expectedResultCount, 'legacy'); - } - }); -} - -function doRipgrepSearchTest(query: ITextQuery, expectedResultCount: number | Function): Promise { +function doSearchTest(query: ITextQuery, expectedResultCount: number | Function): Promise { const engine = new TextSearchEngineAdapter(query); let c = 0; @@ -69,11 +47,6 @@ function doRipgrepSearchTest(query: ITextQuery, expectedResultCount: number | Fu }); } -function doSearchTest(query: ITextQuery, expectedResultCount: number) { - return doLegacySearchTest(query, expectedResultCount) - .then(() => doRipgrepSearchTest(query, expectedResultCount)); -} - suite('Search-integration', function () { this.timeout(1000 * 60); // increase timeout for this suite @@ -240,10 +213,7 @@ suite('Search-integration', function () { maxResults }; - // (Legacy) search can go over the maxResults because it doesn't trim the results from its worker processes to the exact max size. - // But the worst-case scenario should be 2*max-1 - return doLegacySearchTest(config, count => count < maxResults * 2) - .then(() => doRipgrepSearchTest(config, maxResults)); + return doSearchTest(config, maxResults); }); test('Text: a (no results)', () => { @@ -318,7 +288,7 @@ suite('Search-integration', function () { contentPattern: { pattern: '语' } }; - return doRipgrepSearchTest(config, 1).then(results => { + return doSearchTest(config, 1).then(results => { const matchRange = (results[0].results![0]).ranges; assert.deepEqual(matchRange, [{ startLineNumber: 0, @@ -336,7 +306,7 @@ suite('Search-integration', function () { contentPattern: { pattern: 'h\\d,', isRegExp: true } }; - return doRipgrepSearchTest(config, 15).then(results => { + return doSearchTest(config, 15).then(results => { assert.equal(results.length, 3); assert.equal(results[0].results!.length, 1); const match = results[0].results![0]; @@ -353,7 +323,7 @@ suite('Search-integration', function () { afterContext: 2 }; - return doRipgrepSearchTest(config, 4).then(results => { + return doSearchTest(config, 4).then(results => { assert.equal(results.length, 4); assert.equal((results[0].results![0]).lineNumber, 25); assert.equal((results[0].results![0]).text, ' compiler.addUnit(prog,"input.ts");'); @@ -378,7 +348,7 @@ suite('Search-integration', function () { contentPattern: { pattern: 'test' }, }; - return doRipgrepSearchTest(config, 0).then(() => { + return doSearchTest(config, 0).then(() => { throw new Error('expected fail'); }, err => { const searchError = deserializeSearchError(err.message); @@ -394,7 +364,7 @@ suite('Search-integration', function () { contentPattern: { pattern: ')', isRegExp: true }, }; - return doRipgrepSearchTest(config, 0).then(() => { + return doSearchTest(config, 0).then(() => { throw new Error('expected fail'); }, err => { const searchError = deserializeSearchError(err.message); @@ -413,7 +383,7 @@ suite('Search-integration', function () { } }; - return doRipgrepSearchTest(config, 0).then(() => { + return doSearchTest(config, 0).then(() => { throw new Error('expected fail'); }, err => { const searchError = deserializeSearchError(err.message); @@ -429,7 +399,7 @@ suite('Search-integration', function () { contentPattern: { pattern: 'foo\nbar', isRegExp: true } }; - return doRipgrepSearchTest(config, 0).then(() => { + return doSearchTest(config, 0).then(() => { throw new Error('expected fail'); }, err => { const searchError = deserializeSearchError(err.message);