src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts

783 LOC · 552 covered · 231 uncovered · 95 ranges · 73 concepts · 13 introducers · 44 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/base/common/glob.ts · 861 LOCcommon/glob.tssrc/vs/workbench/services/search/common/searchExtConversionTypes.ts · 567 LOCcommon/searchExtConversi…src/vs/workbench/services/search/node/ripgrepSearchUtils.ts · 34 LOCnode/ripgrepSearchUtils.…glob.ts ×1 · 4 introduced LOCglob.ts ×1extHostSearch.test|title=ExtHostSearch File: multiroot sibling exclude clause|occurrence=1 · 0 introduced LOCextHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: basic sibling clause|occurrence=1 · 0 introduced LOCextHostSearch.test|title…search.ts ×3 · 13 introduced LOCsearch.ts ×3extHostSearch.test|title=ExtHostSearch File: include, sibling exclude, and subfolder|occurrence=1 · 0 introduced LOCextHostSearch.test|title…fileSearchManager.ts ×2 · 4 introduced LOCfileSearchManager.ts ×2extHostSearch.test|title=ExtHostSearch File: basic sibling exclude clause|occurrence=1 · 0 introduced LOCextHostSearch.test|title…search.ts ×3 · 13 introduced LOCsearch.ts ×3fileSearchManager.ts ×9 · 57 introduced LOCfileSearchManager.ts ×9search.ts ×1 · 1 introduced LOCsearch.ts ×1textSearchManager.ts ×1 · 2 introduced LOCtextSearchManager.ts ×1extHostSearch.test|title=ExtHostSearch Text: max results = 2|occurrence=1 · 0 introduced LOCextHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: max results = 1|occurrence=1 · 0 introduced LOCextHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: provider returns maxResults exactly|occurrence=1 · 0 introduced LOCextHostSearch.test|title…textSearchManager.ts ×2 · 8 introduced LOCtextSearchManager.ts ×2extHostSearch.test|title=ExtHostSearch Text: basic results|occurrence=1, extHostSearch.test|title=ExtHostSearch Text: provider returns early with limitHit|occurrence=1 · 0 introduced LOCextHostSearch.test|title…excludes combined|occurrence=1 · 0 introduced LOCexcludes combined|occurr…textSearchManager.ts ×1 · 1 introduced LOCtextSearchManager.ts ×1extHostSearch.test|title=ExtHostSearch File: multiroot max results|occurrence=1 · 0 introduced LOCextHostSearch.test|title…textSearchManager.ts ×1 · 3 introduced LOCtextSearchManager.ts ×1search.ts ×1 · 7 introduced LOCsearch.ts ×1search.ts ×1 · 2 introduced LOCsearch.ts ×1excludes resolved correctly|occurrence=1 · 0 introduced LOCexcludes resolved correc…fileSearchManager.ts ×1 · 3 introduced LOCfileSearchManager.ts ×1searchExtConversionTypes.ts ×10 · 60 introduced LOCsearchExtConversionTypes…extHostSearch.test|title=ExtHostSearch File: provider returns maxResults exactly|occurrence=1 · 0 introduced LOCextHostSearch.test|title…fileSearchManager.ts ×1 · 2 introduced LOCfileSearchManager.ts ×1excludes combined|occurrence=1 · 0 introduced LOCexcludes combined|occurr…extHostSearch.test|title=ExtHostSearch File: simple results|occurrence=1 · 0 introduced LOCextHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: works with non-file schemes|occurrence=1 · 0 introduced LOCextHostSearch.test|title…fileSearchManager.ts ×1 · 5 introduced LOCfileSearchManager.ts ×1fileSearchManager.ts ×10 · 32 introduced LOCfileSearchManager.ts ×10excludes|occurrence=1 · 0 introduced LOCexcludes|occurrence=1excludes|occurrence=1 · 0 introduced LOCexcludes|occurrence=1excludes resolved correctly|occurrence=1 · 0 introduced LOCexcludes resolved correc…textSearchManager.ts ×1 · 2 introduced LOCtextSearchManager.ts ×1fileSearchManager.ts ×2 · 3 introduced LOCfileSearchManager.ts ×2search.ts ×2 · 2 introduced LOCsearch.ts ×2fileSearchManager.ts ×4 · 25 introduced LOCfileSearchManager.ts ×4fileSearchManager.ts ×1 · 4 introduced LOCfileSearchManager.ts ×1search.ts ×2 · 7 introduced LOCsearch.ts ×2search.ts ×1 · 4 introduced LOCsearch.ts ×1extHostSearch.test|title=ExtHostSearch File: no results|occurrence=1, extHostSearch.test|title=ExtHostSearch File: provider returns null|occurrence=1 · 0 introduced LOCextHostSearch.test|title…fileSearchManager.ts ×3 · 15 introduced LOCfileSearchManager.ts ×3search.ts ×2 · 2 introduced LOCsearch.ts ×2extHostSearch.test|title=ExtHostSearch Text: no results|occurrence=1 · 0 introduced LOCextHostSearch.test|title…fileSearchManager.ts ×1 · 5 introduced LOCfileSearchManager.ts ×1search.ts ×1 · 3 introduced LOCsearch.ts ×1fileSearchManager.ts ×5 · 42 introduced LOCfileSearchManager.ts ×5searchExtConversionTypes.ts ×2 · 9 introduced LOCsearchExtConversionTypes…textSearchManager.ts ×1 · 3 introduced LOCtextSearchManager.ts ×1search.ts ×1 · 2 introduced LOCsearch.ts ×1search.ts ×1 · 2 introduced LOCsearch.ts ×1searchExtConversionTypes.ts ×6 · 60 introduced LOCsearchExtConversionTypes…extHostSearch.ts ×1 · 3 introduced LOCextHostSearch.ts ×1extHostSearch.test|title=ExtHostSearch File: if onlyFileScheme is set, do not call custom schemes|occurrence=1 · 0 introduced LOCextHostSearch.test|title…fileSearchManager.ts ×5 · 19 introduced LOCfileSearchManager.ts ×5fileSearchManager.ts ×1 · 2 introduced LOCfileSearchManager.ts ×1fileSearchManager.ts ×20 · 133 introduced LOCfileSearchManager.ts ×20fileSearch.ts ×31 · 624 introduced LOCfileSearch.ts ×31ripgrepTextSearchEngine.ts ×4 · 6 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×1 · 2 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×1 · 6 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×1 · 1 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×23 · 125 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×15 · 101 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×8 · 61 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×6 · 31 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×5 · 27 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×6 · 56 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×1 · 16 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×1 · 2 introduced LOCripgrepTextSearchEngine.…ripgrepTextSearchEngine.ts ×23 · 581 introduced LOCripgrepTextSearchEngine.…extHostSearch.test|title=ExtHostSearch File: Search canceled|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: Search canceled|occurrence=1extHostSearch.test|title…excludes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: all provider calls get global include/excludes|occurrence=1excludes|occurrence=1extHostSearch.test|title=ExtHostSearch File: basic sibling exclude clause|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: basic sibling exclude clause|occurrence=1extHostSearch.test|title…excludes combined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: global/local include/excludes combined|occurrence=1excludes combined|occurr…extHostSearch.test|title=ExtHostSearch File: if onlyFileScheme is set, do not call custom schemes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: if onlyFileScheme is set, do not call custom schemes|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: include, sibling exclude, and subfolder|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: include, sibling exclude, and subfolder|occurrence=1extHostSearch.test|title…excludes resolved correctly|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: include/excludes resolved correctly|occurrence=1excludes resolved correc…extHostSearch.test|title=ExtHostSearch File: max results = 1|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: max results = 1|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: max results = 2|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: max results = 2|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: multiroot max results|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: multiroot max results|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: multiroot sibling exclude clause|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: multiroot sibling exclude clause|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: no results|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: no results|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: provider returns maxResults exactly|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: provider returns maxResults exactly|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: provider returns null|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: provider returns null|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: session cancellation should work|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: session cancellation should work|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: simple results|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: simple results|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch File: works with non-file schemes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch File: works with non-file schemes|occurrence=1extHostSearch.test|title…excludes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: all provider calls get global include/excludes|occurrence=1excludes|occurrence=1extHostSearch.test|title=ExtHostSearch Text: basic results|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: basic results|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: basic sibling clause|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: basic sibling clause|occurrence=1extHostSearch.test|title…excludes combined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: global/local include/excludes combined|occurrence=1excludes combined|occurr…extHostSearch.test|title=ExtHostSearch Text: include pattern applied|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: include pattern applied|occurrence=1extHostSearch.test|title…excludes resolved correctly|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: include/excludes resolved correctly|occurrence=1excludes resolved correc…extHostSearch.test|title=ExtHostSearch Text: max results = 1|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: max results = 1|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: max results = 2|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: max results = 2|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: multiroot max results|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: multiroot max results|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: multiroot sibling clause|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: multiroot sibling clause|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: no results|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: no results|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: provider fail|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: provider fail|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: provider returns early with limitHit|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: provider returns early with limitHit|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: provider returns maxResults exactly|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: provider returns maxResults exactly|occurrence=1extHostSearch.test|title…extHostSearch.test|title=ExtHostSearch Text: works with non-file schemes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostSearch.test|title=ExtHostSearch Text: works with non-file schemes|occurrence=1extHostSearch.test|title…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser chopped-up input chunks|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser chopped-up input chunks|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser empty result (#100569)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser empty result (#100569)|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser multiple results|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser multiple results|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser multiple submatches with newline in between (#131507)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser multiple submatches with newline in between (#131507)|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser multiple submatches without newline in between (#131507)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser multiple submatches without newline in between (#131507)|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser single result|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine RipgrepParser single result|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine brace expansion for ripgrep|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine brace expansion for ripgrep|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine fixNewline - matching|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine fixNewline - matching|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine fixRegexNewline - re|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine fixRegexNewline - re|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine fixRegexNewline - src|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine fixRegexNewline - src|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine getRgArgs simple includes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine getRgArgs simple includes|occurrence=1ripgrepTextSearchEngineU…ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine unicodeEscapesToPCRE2|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test|title=RipgrepTextSearchEngine unicodeEscapesToPCRE2|occurrence=1ripgrepTextSearchEngineU…Focused file · src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts · 783 LOCnode/ripgrepTextSearchEn…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- ripgrepTextSearchEngine.ts ×23
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as cp from 'child_process';
7 > import { EventEmitter } from 'events';
8 > import { StringDecoder } from 'string_decoder';
9 > import { coalesce, mapArrayOrNot } from '../../../../base/common/arrays.js';
10 > import { CancellationToken } from '../../../../base/common/cancellation.js';
11 > import { groupBy } from '../../../../base/common/collections.js';
12 > import { splitGlobAware } from '../../../../base/common/glob.js';
13 > import { createRegExp, escapeRegExpCharacters } from '../../../../base/common/strings.js';
14 > import { URI } from '../../../../base/common/uri.js';
15 > import { Progress } from '../../../../platform/progress/common/progress.js';
16 > import { DEFAULT_MAX_SEARCH_RESULTS, ITextSearchPreviewOptions, SearchError, SearchErrorCode, serializeSearchError, TextSearchMatch } from '../common/search.js';
17 > import { Range, TextSearchComplete2, TextSearchContext2, TextSearchMatch2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2 } from '../common/searchExtTypes.js';
18 > import { AST as ReAST, RegExpParser, RegExpVisitor } from 'vscode-regexpp';
19 > import { anchorGlob, IOutputChannel, Maybe, rangeToSearchRange, searchRangeToRange } from './ripgrepSearchUtils.js';
20 > import type { RipgrepTextSearchOptions } from '../common/searchExtTypesInternal.js';
21 > import { newToOldPreviewOptions } from '../common/searchExtConversionTypes.js';
22 > import { rgDiskPath } from '../../../../base/node/ripgrep.js';
23 >
24 > export class RipgrepTextSearchEngine {
25 >
26 > constructor(private outputChannel: IOutputChannel, private readonly _numThreads?: number | undefined) { }
27 >
28 > provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: Progress<TextSearchResult2>, token: CancellationToken): Promise<TextSearchComplete2> {
29 return Promise.all(options.folderOptions.map(folderOption => {
30 const extendedOptions: RipgrepTextSearchOptions = {
31 folderOptions: folderOption,
32 numThreads: this._numThreads,
33 maxResults: options.maxResults,
34 previewOptions: options.previewOptions,
35 maxFileSize: options.maxFileSize,
36 surroundingContext: options.surroundingContext
37 };
38 return this.provideTextSearchResultsWithRgOptions(query, extendedOptions, progress, token);
39 })).then((e => {
40 const complete: TextSearchComplete2 = {
41 // todo: get this to actually check
42 limitHit: e.some(complete => !!complete && complete.limitHit)
43 };
44 return complete;
45 }));
46 }
48 > async provideTextSearchResultsWithRgOptions(query: TextSearchQuery2, options: RipgrepTextSearchOptions, progress: Progress<TextSearchResult2>, token: CancellationToken): Promise<TextSearchComplete2> {
49 this.outputChannel.appendLine(`provideTextSearchResults ${query.pattern}, ${JSON.stringify({
50 ...options,
51 ...{
52 folder: options.folderOptions.folder.toString()
53 }
54 })}`);
55
56 if (!query.pattern) {
57 return { limitHit: false };
58 }
59
60 const resolvedRgDiskPath = await rgDiskPath();
61
62 return new Promise((resolve, reject) => {
63 token.onCancellationRequested(() => cancel());
64
65 const extendedOptions: RipgrepTextSearchOptions = {
66 ...options,
67 numThreads: this._numThreads
68 };
69 const rgArgs = getRgArgs(query, extendedOptions);
70
71 const cwd = options.folderOptions.folder.fsPath;
72
73 const escapedArgs = rgArgs
74 .map(arg => arg.match(/^-/) ? arg : `'${arg}'`)
75 .join(' ');
76 this.outputChannel.appendLine(`${resolvedRgDiskPath} ${escapedArgs}\n - cwd: ${cwd}`);
77
78 let rgProc: Maybe<cp.ChildProcess> = cp.spawn(resolvedRgDiskPath, rgArgs, { cwd });
79 rgProc.on('error', e => {
80 console.error(e);
81 this.outputChannel.appendLine('Error: ' + (e && e.message));
82 reject(serializeSearchError(new SearchError(e && e.message, SearchErrorCode.rgProcessError)));
83 });
84
85 let gotResult = false;
86 const ripgrepParser = new RipgrepParser(options.maxResults ?? DEFAULT_MAX_SEARCH_RESULTS, options.folderOptions.folder, newToOldPreviewOptions(options.previewOptions));
87 ripgrepParser.on('result', (match: TextSearchResult2) => {
88 gotResult = true;
89 dataWithoutResult = '';
90 progress.report(match);
91 });
92
93 let isDone = false;
94 const cancel = () => {
95 isDone = true;
96
97 rgProc?.kill();
98
99 ripgrepParser?.cancel();
100 };
101
102 let limitHit = false;
103 ripgrepParser.on('hitLimit', () => {
104 limitHit = true;
105 cancel();
106 });
107
108 let dataWithoutResult = '';
109 rgProc.stdout!.on('data', data => {
110 ripgrepParser.handleData(data);
111 if (!gotResult) {
112 dataWithoutResult += data;
113 }
114 });
115
116 let gotData = false;
117 rgProc.stdout!.once('data', () => gotData = true);
118
119 let stderr = '';
120 rgProc.stderr!.on('data', data => {
121 const message = data.toString();
122 this.outputChannel.appendLine(message);
123
124 if (stderr.length + message.length < 1e6) {
125 stderr += message;
126 }
127 });
128
129 rgProc.on('close', () => {
130 this.outputChannel.appendLine(gotData ? 'Got data from stdout' : 'No data from stdout');
131 this.outputChannel.appendLine(gotResult ? 'Got result from parser' : 'No result from parser');
132 if (dataWithoutResult) {
133 this.outputChannel.appendLine(`Got data without result: ${dataWithoutResult}`);
134 }
135
136 this.outputChannel.appendLine('');
137
138 if (isDone) {
139 resolve({ limitHit });
140 } else {
141 // Trigger last result
142 ripgrepParser.flush();
143 rgProc = null;
144 let searchError: Maybe<SearchError>;
145 if (stderr && !gotData && (searchError = rgErrorMsgForDisplay(stderr))) {
146 reject(serializeSearchError(new SearchError(searchError.message, searchError.code)));
147 } else {
148 resolve({ limitHit });
149 }
150 }
151 });
152 });
153 }
155 >
156 > /**
157 > * Read the first line of stderr and return an error for display or undefined, based on a list of
158 > * allowed properties.
159 > * Ripgrep produces stderr output which is not from a fatal error, and we only want the search to be
160 > * "failed" when a fatal error was produced.
161 > */
162 function rgErrorMsgForDisplay(msg: string): Maybe<SearchError> {
163 const lines = msg.split('\n');
164 const firstLine = lines[0].trim();
165
166 if (lines.some(l => l.startsWith('regex parse error'))) {
167 return new SearchError(buildRegexParseError(lines), SearchErrorCode.regexParseError);
168 }
169
170 const match = firstLine.match(/grep config error: unknown encoding: (.*)/);
171 if (match) {
172 return new SearchError(`Unknown encoding: ${match[1]}`, SearchErrorCode.unknownEncoding);
173 }
174
175 if (firstLine.startsWith('error parsing glob')) {
176 // Uppercase first letter
177 return new SearchError(firstLine.charAt(0).toUpperCase() + firstLine.substr(1), SearchErrorCode.globParseError);
178 }
179
180 if (firstLine.startsWith('the literal')) {
181 // Uppercase first letter
182 return new SearchError(firstLine.charAt(0).toUpperCase() + firstLine.substr(1), SearchErrorCode.invalidLiteral);
183 }
184
185 if (firstLine.startsWith('PCRE2: error compiling pattern')) {
186 return new SearchError(firstLine, SearchErrorCode.regexParseError);
187 }
188
189 return undefined;
190 }
192 function buildRegexParseError(lines: string[]): string {
193 const errorMessage: string[] = ['Regex parse error'];
194 const pcre2ErrorLine = lines.filter(l => (l.startsWith('PCRE2:')));
195 if (pcre2ErrorLine.length >= 1) {
196 const pcre2ErrorMessage = pcre2ErrorLine[0].replace('PCRE2:', '');
197 if (pcre2ErrorMessage.indexOf(':') !== -1 && pcre2ErrorMessage.split(':').length >= 2) {
198 const pcre2ActualErrorMessage = pcre2ErrorMessage.split(':')[1];
199 errorMessage.push(':' + pcre2ActualErrorMessage);
200 }
201 }
202
203 return errorMessage.join('');
204 }
206 >
207 > export class RipgrepParser extends EventEmitter {
208 > private remainder = '';
209 > private isDone = false;
210 > private hitLimit = false;
211 > private stringDecoder: StringDecoder;
212 >
213 > private numResults = 0;
214 >
215 > constructor(private maxResults: number, private root: URI, private previewOptions: ITextSearchPreviewOptions) {
217 > this.stringDecoder = new StringDecoder();
218 > }
220 > cancel(): void {
221 this.isDone = true;
222 }
224 > flush(): void {
225 > this.handleDecodedData(this.stringDecoder.end()); ripgrepTextSearchEngine.ts ×23
226 > }
228 >
229 > override on(event: 'result', listener: (result: TextSearchResult2) => void): this;
230 > override on(event: 'hitLimit', listener: () => void): this;
231 > override on(event: string, listener: (...args: any[]) => void): this {
232 > super.on(event, listener); ripgrepTextSearchEngine.ts ×23
233 > return this;
234 > }
236 > handleData(data: Buffer | string): void {
237 > if (this.isDone) { ripgrepTextSearchEngine.ts ×23
238 return;
239 }
241 > const dataStr = typeof data === 'string' ? data : this.stringDecoder.write(data);
242 > this.handleDecodedData(dataStr);
243 > }
245 > private handleDecodedData(decodedData: string): void {
246 > // check for newline before appending to remainder ripgrepTextSearchEngine.ts ×23
247 > let newlineIdx = decodedData.indexOf('\n');
248 >
249 > // If the previous data chunk didn't end in a newline, prepend it to this chunk
250 > const dataStr = this.remainder + decodedData;
251 >
252 > if (newlineIdx >= 0) {
253 > newlineIdx += this.remainder.length;
254 > } else {
255 > // Shortcut
256 > this.remainder = dataStr;
257 > return;
258 > }
259 >
260 > let prevIdx = 0;
261 > while (newlineIdx >= 0) {
262 > this.handleLine(dataStr.substring(prevIdx, newlineIdx).trim());
263 > prevIdx = newlineIdx + 1;
264 > newlineIdx = dataStr.indexOf('\n', prevIdx);
265 > }
266 >
267 > this.remainder = dataStr.substring(prevIdx);
268 > }
270 >
271 > private handleLine(outputLine: string): void {
272 > if (this.isDone || !outputLine) { ripgrepTextSearchEngine.ts ×23
274 > }
276 > let parsedLine: IRgMessage;
277 > try {
278 > parsedLine = JSON.parse(outputLine);
279 > } catch (e) {
280 throw new Error(`malformed line from rg: ${outputLine}`);
281 }
283 > if (parsedLine.type === 'match') {
284 > const matchPath = bytesOrTextToString(parsedLine.data.path);
285 > const uri = URI.joinPath(this.root, matchPath);
286 > const result = this.createTextSearchMatch(parsedLine.data, uri);
287 > this.onResult(result);
288 >
289 > if (this.hitLimit) {
290 this.cancel();
291 this.emit('hitLimit');
292 }
293 > } else if (parsedLine.type === 'context') { ripgrepTextSearchEngine.ts ×23
294 const contextPath = bytesOrTextToString(parsedLine.data.path);
295 const uri = URI.joinPath(this.root, contextPath);
296 const result = this.createTextSearchContexts(parsedLine.data, uri);
297 result.forEach(r => this.onResult(r));
298 }
301 > private createTextSearchMatch(data: IRgMatch, uri: URI): TextSearchMatch2 {
302 > const lineNumber = data.line_number - 1; ripgrepTextSearchEngine.ts ×23
303 > const fullText = bytesOrTextToString(data.lines);
304 > const fullTextBytes = Buffer.from(fullText);
305 >
306 > let prevMatchEnd = 0;
307 > let prevMatchEndCol = 0;
308 > let prevMatchEndLine = lineNumber;
309 >
310 > // it looks like certain regexes can match a line, but cause rg to not
311 > // emit any specific submatches for that line.
312 > // https://github.com/microsoft/vscode/issues/100569#issuecomment-738496991
313 > if (data.submatches.length === 0) {
314 > data.submatches.push( ripgrepTextSearchEngine.ts ×1
315 > fullText.length
316 > ? { start: 0, end: 1, match: { text: fullText[0] } }
317 > : { start: 0, end: 0, match: { text: '' } }
318 > );
319 > }
321 > const ranges = coalesce(data.submatches.map((match, i) => {
322 > if (this.hitLimit) {
323 return null;
324 }
326 > this.numResults++;
327 > if (this.numResults >= this.maxResults) {
328 // Finish the line, then report the result below
329 this.hitLimit = true;
330 }
332 > const matchText = bytesOrTextToString(match.match);
333 >
334 > const inBetweenText = fullTextBytes.slice(prevMatchEnd, match.start).toString();
335 > const inBetweenStats = getNumLinesAndLastNewlineLength(inBetweenText);
336 > const startCol = inBetweenStats.numLines > 0 ?
337 > inBetweenStats.lastLineLength : ripgrepTextSearchEngine.ts ×4
338 > inBetweenStats.lastLineLength + prevMatchEndCol; ripgrepTextSearchEngine.ts ×23
339 >
340 > const stats = getNumLinesAndLastNewlineLength(matchText);
341 > const startLineNumber = inBetweenStats.numLines + prevMatchEndLine;
342 > const endLineNumber = stats.numLines + startLineNumber;
343 > const endCol = stats.numLines > 0 ?
344 > stats.lastLineLength : ripgrepTextSearchEngine.ts ×4
345 > stats.lastLineLength + startCol; ripgrepTextSearchEngine.ts ×1
347 > prevMatchEnd = match.end;
348 > prevMatchEndCol = endCol;
349 > prevMatchEndLine = endLineNumber;
350 >
351 > return new Range(startLineNumber, startCol, endLineNumber, endCol);
352 > }));
353 >
354 > const searchRange = mapArrayOrNot(<Range[]>ranges, rangeToSearchRange);
355 >
356 > const internalResult = new TextSearchMatch(fullText, searchRange, this.previewOptions);
357 > return new TextSearchMatch2(
358 > uri,
359 > internalResult.rangeLocations.map(e => (
360 > {
361 > sourceRange: searchRangeToRange(e.source),
362 > previewRange: searchRangeToRange(e.preview),
363 > }
364 > )),
365 > internalResult.previewText);
366 > }
368 > private createTextSearchContexts(data: IRgMatch, uri: URI): TextSearchContext2[] {
369 const text = bytesOrTextToString(data.lines);
370 const startLine = data.line_number;
371 return text
372 .replace(/\r?\n$/, '')
373 .split('\n')
374 .map((line, i) => new TextSearchContext2(uri, line, startLine + i));
375 }
377 > private onResult(match: TextSearchResult2): void {
378 > this.emit('result', match); ripgrepTextSearchEngine.ts ×23
379 > }
381 >
382 > function bytesOrTextToString(obj: any): string { ripgrepTextSearchEngine.ts ×23
383 > return obj.bytes ?
384 Buffer.from(obj.bytes, 'base64').toString() :
386 > }
388 > function getNumLinesAndLastNewlineLength(text: string): { numLines: number; lastLineLength: number } { ripgrepTextSearchEngine.ts ×23
389 > const re = /\n/g;
390 > let numLines = 0;
391 > let lastNewlineIdx = -1;
392 > let match: ReturnType<typeof re.exec>;
393 > while (match = re.exec(text)) {
394 > numLines++; ripgrepTextSearchEngine.ts ×4
395 > lastNewlineIdx = match.index;
396 > }
398 > const lastLineLength = lastNewlineIdx >= 0 ?
399 > text.length - lastNewlineIdx - 1 : ripgrepTextSearchEngine.ts ×4
400 > text.length; ripgrepTextSearchEngine.ts ×23
401 >
402 > return { numLines, lastLineLength };
403 > }
405 > // exported for testing
406 > export function getRgArgs(query: TextSearchQuery2, options: RipgrepTextSearchOptions): string[] {
407 > const args = ['--hidden', '--no-require-git']; ripgrepTextSearchEngine.ts ×15
408 > args.push(query.isCaseSensitive ? '--case-sensitive' : '--ignore-case');
409 >
410 > if (options.folderOptions.ignoreGlobCase) {
411 args.push('--glob-case-insensitive');
412 args.push('--ignore-file-case-insensitive');
413 }
415 > const { doubleStarIncludes, otherIncludes } = groupBy(
416 > options.folderOptions.includes,
417 > (include: string) => include.startsWith('**') ? 'doubleStarIncludes' : 'otherIncludes');
418 >
419 > if (otherIncludes && otherIncludes.length) {
420 > const uniqueOthers = new Set<string>();
421 > otherIncludes.forEach(other => { uniqueOthers.add(other); });
422 >
423 > args.push('-g', '!*');
424 > uniqueOthers
425 > .forEach(otherIncude => {
426 > spreadGlobComponents(otherIncude)
427 > .map(anchorGlob)
428 > .forEach(globArg => {
429 > args.push('-g', globArg);
430 > });
431 > });
432 > }
433 >
434 > if (doubleStarIncludes && doubleStarIncludes.length) {
435 > doubleStarIncludes.forEach(globArg => {
436 > args.push('-g', globArg);
437 > });
438 > }
439 >
440 > options.folderOptions.excludes.map(e => typeof (e) === 'string' ? e : e.pattern)
441 > .map(anchorGlob)
442 > .forEach(rgGlob => args.push('-g', `!${rgGlob}`));
443 >
444 > if (options.maxFileSize) {
445 args.push('--max-filesize', options.maxFileSize + '');
446 }
448 > if (options.folderOptions.useIgnoreFiles.local) {
449 if (!options.folderOptions.useIgnoreFiles.parent) {
450 args.push('--no-ignore-parent');
451 }
453 > // Don't use .gitignore or .ignore
454 > args.push('--no-ignore');
455 > }
456 >
457 > if (options.folderOptions.followSymlinks) {
458 args.push('--follow');
459 }
461 > if (options.folderOptions.encoding && options.folderOptions.encoding !== 'utf8') {
462 args.push('--encoding', options.folderOptions.encoding);
463 }
465 > if (options.numThreads) {
466 args.push('--threads', `${options.numThreads}`);
467 }
469 > // Ripgrep handles -- as a -- arg separator. Only --.
470 > // - is ok, --- is ok, --some-flag is also ok. Need to special case.
471 > if (query.pattern === '--') {
472 query.isRegExp = true;
473 query.pattern = '\\-\\-';
474 }
476 > if (query.isMultiline && !query.isRegExp) {
477 query.pattern = escapeRegExpCharacters(query.pattern);
478 query.isRegExp = true;
479 }
481 > // Allow $ to match /r/n
482 > args.push('--crlf');
483 >
484 > if (query.isRegExp) {
485 query.pattern = unicodeEscapesToPCRE2(query.pattern);
486 args.push('--engine', 'auto');
487 }
489 > let searchPatternAfterDoubleDashes: Maybe<string>;
490 > if (query.isWordMatch) {
491 const regexp = createRegExp(query.pattern, !!query.isRegExp, { wholeWord: query.isWordMatch });
492 const regexpStr = regexp.source.replace(/\\\//g, '/'); // RegExp.source arbitrarily returns escaped slashes. Search and destroy.
493 args.push('--regexp', regexpStr);
494 > } else if (query.isRegExp) { ripgrepTextSearchEngine.ts ×15
495 let fixedRegexpQuery = fixRegexNewline(query.pattern);
496 fixedRegexpQuery = fixNewline(fixedRegexpQuery);
497 args.push('--regexp', fixedRegexpQuery);
499 > searchPatternAfterDoubleDashes = query.pattern;
500 > args.push('--fixed-strings');
501 > }
502 >
503 > args.push('--no-config');
504 > if (!options.folderOptions.useIgnoreFiles.global) {
505 > args.push('--no-ignore-global');
506 > }
507 >
508 > args.push('--json');
509 >
510 > if (query.isMultiline) {
511 args.push('--multiline');
512 }
514 > if (options.surroundingContext) {
515 args.push('--before-context', options.surroundingContext + '');
516 args.push('--after-context', options.surroundingContext + '');
517 }
519 > // Folder to search
520 > args.push('--');
521 >
522 > if (searchPatternAfterDoubleDashes) {
523 > // Put the query after --, in case the query starts with a dash
524 > args.push(searchPatternAfterDoubleDashes);
525 > }
526 >
527 > args.push('.');
528 >
529 > return args;
530 > }
532 > /**
533 > * `"foo/*bar/something"` -> `["foo", "foo/*bar", "foo/*bar/something", "foo/*bar/something/**"]`
534 > */
535 > function spreadGlobComponents(globComponent: string): string[] { ripgrepTextSearchEngine.ts ×15
536 > const globComponentWithBraceExpansion = performBraceExpansionForRipgrep(globComponent);
537 >
538 > return globComponentWithBraceExpansion.flatMap((globArg) => {
539 > const components = splitGlobAware(globArg, '/');
540 > return components.map((_, i) => components.slice(0, i + 1).join('/'));
541 > });
542 >
543 > }
545 > export function unicodeEscapesToPCRE2(pattern: string): string {
546 > // Match \u1234 ripgrepTextSearchEngine.ts ×1
547 > const unicodePattern = /((?:[^\\]|^)(?:\\\\)*)\\u([a-z0-9]{4})/gi;
548 >
549 > while (pattern.match(unicodePattern)) {
550 > pattern = pattern.replace(unicodePattern, `$1\\x{$2}`);
551 > }
552 >
553 > // Match \u{1234}
554 > // \u with 5-6 characters will be left alone because \x only takes 4 characters.
555 > const unicodePatternWithBraces = /((?:[^\\]|^)(?:\\\\)*)\\u\{([a-z0-9]{4})\}/gi;
556 > while (pattern.match(unicodePatternWithBraces)) {
557 > pattern = pattern.replace(unicodePatternWithBraces, `$1\\x{$2}`);
558 > }
559 >
560 > return pattern;
561 > }
563 > export interface IRgMessage {
564 > type: 'match' | 'context' | string;
565 > data: IRgMatch;
566 > }
567 >
568 > export interface IRgMatch {
569 > path: IRgBytesOrText;
570 > lines: IRgBytesOrText;
571 > line_number: number;
572 > absolute_offset: number;
573 > submatches: IRgSubmatch[];
574 > }
575 >
576 > export interface IRgSubmatch {
577 > match: IRgBytesOrText;
578 > start: number;
579 > end: number;
580 > }
581 >
582 > export type IRgBytesOrText = { bytes: string } | { text: string };
583 >
584 > const isLookBehind = (node: ReAST.Node) => node.type === 'Assertion' && node.kind === 'lookbehind';
585 >
586 > export function fixRegexNewline(pattern: string): string {
587 > // we parse the pattern anew each tiem ripgrepTextSearchEngine.ts ×6
588 > let re: ReAST.Pattern;
589 > try {
590 > re = new RegExpParser().parsePattern(pattern);
591 > } catch {
592 > return pattern; ripgrepTextSearchEngine.ts ×5
593 > }
595 > let output = '';
596 > let lastEmittedIndex = 0;
597 > const replace = (start: number, end: number, text: string) => {
598 > output += pattern.slice(lastEmittedIndex, start) + text;
599 > lastEmittedIndex = end;
600 > };
601 >
602 > const context: ReAST.Node[] = [];
603 > const visitor = new RegExpVisitor({
604 > onCharacterEnter(char) {
605 > if (char.raw !== '\\n') {
606 > return;
607 > }
608 >
609 > const parent = context[0];
610 > if (!parent) {
611 > // simple char, \n -> \r?\n
612 > replace(char.start, char.end, '\\r?\\n');
613 > } else if (context.some(isLookBehind)) {
614 > // no-op in a lookbehind, see #100569 ripgrepTextSearchEngine.ts ×5
615 > } else if (parent.type === 'CharacterClass') { ripgrepTextSearchEngine.ts ×6
616 > if (parent.negate) { ripgrepTextSearchEngine.ts ×5
617 > // negative bracket expr, [^a-z\n] -> (?![a-z]|\r?\n)
618 > const otherContent = pattern.slice(parent.start + 2, char.start) + pattern.slice(char.end, parent.end - 1);
619 > if (parent.parent?.type === 'Quantifier') {
620 > // If quantified, we can't use a negative lookahead in a quantifier.
621 > // But `.` already doesn't match new lines, so we can just use that
622 > // (with any other negations) instead.
623 > replace(parent.start, parent.end, otherContent ? `[^${otherContent}]` : '.');
624 > } else {
625 > replace(parent.start, parent.end, '(?!\\r?\\n' + (otherContent ? `|[${otherContent}]` : '') + ')');
626 > }
627 > } else {
628 > // positive bracket expr, [a-z\n] -> (?:[a-z]|\r?\n)
629 > const otherContent = pattern.slice(parent.start + 1, char.start) + pattern.slice(char.end, parent.end - 1);
630 > replace(parent.start, parent.end, otherContent === '' ? '\\r?\\n' : `(?:[${otherContent}]|\\r?\\n)`);
631 > }
632 > } else if (parent.type === 'Quantifier') { ripgrepTextSearchEngine.ts ×6
633 > replace(char.start, char.end, '(?:\\r?\\n)');
634 > }
635 > },
636 > onQuantifierEnter(node) {
637 > context.unshift(node);
638 > },
639 > onQuantifierLeave() {
640 > context.shift();
641 > },
642 > onCharacterClassRangeEnter(node) {
643 > context.unshift(node);
644 > },
645 > onCharacterClassRangeLeave() {
646 > context.shift();
647 > },
648 > onCharacterClassEnter(node) {
649 > context.unshift(node);
650 > },
651 > onCharacterClassLeave() {
652 > context.shift();
653 > },
654 > onAssertionEnter(node) {
655 > if (isLookBehind(node)) { ripgrepTextSearchEngine.ts ×5
656 > context.push(node);
657 > }
658 > },
659 > onAssertionLeave(node) { ripgrepTextSearchEngine.ts ×6
660 > if (context[0] === node) { ripgrepTextSearchEngine.ts ×5
661 > context.shift();
662 > }
663 > },
665 >
666 > visitor.visit(re);
667 > output += pattern.slice(lastEmittedIndex);
668 > return output;
669 > }
671 > export function fixNewline(pattern: string): string {
672 > return pattern.replace(/\n/g, '\\r?\\n'); ripgrepTextSearchEngine.ts ×1
673 > }
675 > // brace expansion for ripgrep
676 >
677 > /**
678 > * Split string given first opportunity for brace expansion in the string.
679 > * - If the brace is prepended by a \ character, then it is escaped.
680 > * - Does not process escapes that are within the sub-glob.
681 > * - If two unescaped `{` occur before `}`, then ripgrep will return an error for brace nesting, so don't split on those.
682 > */
683 > function getEscapeAwareSplitStringForRipgrep(pattern: string): { fixedStart?: string; strInBraces: string; fixedEnd?: string } { ripgrepTextSearchEngine.ts ×6
684 > let inBraces = false;
685 > let escaped = false;
686 > let fixedStart = '';
687 > let strInBraces = '';
688 > for (let i = 0; i < pattern.length; i++) {
689 > const char = pattern[i];
690 > switch (char) {
691 > case '\\':
692 > if (escaped) { ripgrepTextSearchEngine.ts ×8
693 > // If we're already escaped, then just leave the escaped slash and the preceeding slash that escapes it.
694 > // The two escaped slashes will result in a single slash and whatever processes the glob later will properly process the escape
695 > if (inBraces) {
696 > strInBraces += '\\' + char;
697 > } else {
698 fixedStart += '\\' + char;
699 }
700 > escaped = false; ripgrepTextSearchEngine.ts ×8
701 > } else {
702 > escaped = true;
703 > }
704 > break;
706 > if (escaped) { ripgrepTextSearchEngine.ts ×8
707 > // if we escaped this opening bracket, then it is to be taken literally. Remove the `\` because we've acknowleged it and add the `{` to the appropriate string
708 > if (inBraces) {
709 strInBraces += char;
711 > fixedStart += char;
712 > }
713 > escaped = false;
714 > } else {
715 > if (inBraces) {
716 > // ripgrep treats this as attempting to do a nested alternate group, which is invalid. Return with pattern including changes from escaped braces.
717 > return { strInBraces: fixedStart + '{' + strInBraces + '{' + pattern.substring(i + 1) };
718 > } else {
719 > inBraces = true;
720 > }
721 > }
722 > break;
724 > if (escaped) { ripgrepTextSearchEngine.ts ×8
725 > // same as `}`, but for closing bracket
726 > if (inBraces) {
727 > strInBraces += char;
728 > } else {
729 fixedStart += char;
730 }
731 > escaped = false; ripgrepTextSearchEngine.ts ×8
732 > } else if (inBraces) {
733 > // we found an end bracket to a valid opening bracket. Return the appropriate strings.
734 > return { fixedStart, strInBraces, fixedEnd: pattern.substring(i + 1) };
735 > } else {
736 > // if we're not in braces and not escaped, then this is a literal `}` character and we're still adding to fixedStart.
737 > fixedStart += char;
738 > }
739 > break;
741 > // similar to the `\\` case, we didn't do anything with the escape, so we should re-insert it into the appropriate string
742 > // to be consumed later when individual parts of the glob are processed
743 > if (inBraces) {
744 > strInBraces += (escaped ? '\\' : '') + char; ripgrepTextSearchEngine.ts ×8
746 > fixedStart += (escaped ? '\\' : '') + char;
747 > }
748 > escaped = false;
749 > break;
750 > }
751 > }
752 >
753 >
754 > // we are haven't hit the last brace, so no splitting should occur. Return with pattern including changes from escaped braces.
755 > return { strInBraces: fixedStart + (inBraces ? ('{' + strInBraces) : '') };
756 > }
758 > /**
759 > * Parses out curly braces and returns equivalent globs. Only supports one level of nesting.
760 > * Exported for testing.
761 > */
762 > export function performBraceExpansionForRipgrep(pattern: string): string[] {
763 > const { fixedStart, strInBraces, fixedEnd } = getEscapeAwareSplitStringForRipgrep(pattern); ripgrepTextSearchEngine.ts ×6
764 > if (fixedStart === undefined || fixedEnd === undefined) {
765 > return [strInBraces];
766 > }
768 > let arr = splitGlobAware(strInBraces, ',');
769 >
770 > if (!arr.length) {
771 > // occurs if the braces are empty.
772 > arr = [''];
773 > }
774 >
775 > const ends = performBraceExpansionForRipgrep(fixedEnd);
776 >
777 > return arr.flatMap((elem) => {
778 > const start = fixedStart + elem;
779 > return ends.map((end) => {
780 > return start + end;
781 > });
782 > });
783 > }