ripgrepTextSearchEngine.ts ×23

Frontier kind: Code frontier

unlabeled · c_1bb6ad099b0d

44 tests · 11139 LOC · 47 files · introduces 0 tests · 581 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
32 ranges581 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1588 ranges11139 lines · 47 files · Browse complete extent
All tests (intent)
44 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

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 native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

2 files ranked by introduced lines: 581 introduced LOC across 32 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/services/search/common/searchExtConversionTypes.ts 455 introduced LOC · 9 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- searchExtConversionTypes.ts
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 { asArray, coalesce } from '../../../../base/common/arrays.js';
7 > import { CancellationToken } from '../../../../base/common/cancellation.js';
8 > import { URI } from '../../../../base/common/uri.js';
9 > import { IProgress } from '../../../../platform/progress/common/progress.js';
10 > import { DEFAULT_TEXT_SEARCH_PREVIEW_OPTIONS } from './search.js';
11 > import { Range, FileSearchProvider2, FileSearchProviderOptions, ProviderResult, TextSearchComplete2, TextSearchContext2, TextSearchMatch2, TextSearchProvider2, TextSearchProviderOptions, TextSearchQuery2, TextSearchResult2, TextSearchCompleteMessage } from './searchExtTypes.js';
12 >
13 > // old types that are retained for backward compatibility
14 > // TODO: delete this when search apis are adopted by all first-party extensions
15 >
16 > /**
17 > * A relative pattern is a helper to construct glob patterns that are matched
18 > * relatively to a base path. The base path can either be an absolute file path
19 > * or a [workspace folder](#WorkspaceFolder).
20 > */
21 > export interface RelativePattern {
22 >
23 > /**
24 > * A base file path to which this pattern will be matched against relatively.
25 > */
26 > base: string;
27 >
28 > /**
29 > * A file glob pattern like `*.{ts,js}` that will be matched on file paths
30 > * relative to the base path.
31 > *
32 > * Example: Given a base of `/home/work/folder` and a file path of `/home/work/folder/index.js`,
33 > * the file glob pattern will match on `index.js`.
34 > */
35 > pattern: string;
36 > }
37 >
38 > /**
39 > * A file glob pattern to match file paths against. This can either be a glob pattern string
40 > * (like `** /*.{ts,js}` without space before / or `*.{ts,js}`) or a [relative pattern](#RelativePattern).
41 > *
42 > * Glob patterns can have the following syntax:
43 > * * `*` to match zero or more characters in a path segment
44 > * * `?` to match on one character in a path segment
45 > * * `**` to match any number of path segments, including none
46 > * * `{}` to group conditions (e.g. `** /*.{ts,js}` without space before / matches all TypeScript and JavaScript files)
47 > * * `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
48 > * * `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
49 > *
50 > * Note: a backslash (`\`) is not valid within a glob pattern. If you have an existing file
51 > * path to match against, consider to use the [relative pattern](#RelativePattern) support
52 > * that takes care of converting any backslash into slash. Otherwise, make sure to convert
53 > * any backslash to slash when creating the glob pattern.
54 > */
55 > export type GlobPattern = string | RelativePattern;
56 >
57 > /**
58 > * The parameters of a query for text search.
59 > */
60 > export interface TextSearchQuery {
61 > /**
62 > * The text pattern to search for.
63 > */
64 > pattern: string;
65 >
66 > /**
67 > * Whether or not `pattern` should match multiple lines of text.
68 > */
69 > isMultiline?: boolean;
70 >
71 > /**
72 > * Whether or not `pattern` should be interpreted as a regular expression.
73 > */
74 > isRegExp?: boolean;
75 >
76 > /**
77 > * Whether or not the search should be case-sensitive.
78 > */
79 > isCaseSensitive?: boolean;
80 >
81 > /**
82 > * Whether or not to search for whole word matches only.
83 > */
84 > isWordMatch?: boolean;
85 > }
86 >
87 > /**
88 > * A file glob pattern to match file paths against.
89 > * TODO@roblou - merge this with the GlobPattern docs/definition in vscode.d.ts.
90 > * @see [GlobPattern](#GlobPattern)
91 > */
92 > export type GlobString = string;
93 >
94 > /**
95 > * Options common to file and text search
96 > */
97 > export interface SearchOptions {
98 > /**
99 > * The root folder to search within.
100 > */
101 > folder: URI;
102 >
103 > /**
104 > * Files that match an `includes` glob pattern should be included in the search.
105 > */
106 > includes: GlobString[];
107 >
108 > /**
109 > * Files that match an `excludes` glob pattern should be excluded from the search.
110 > */
111 > excludes: GlobString[];
112 >
113 > /**
114 > * Whether external files that exclude files, like .gitignore, should be respected.
115 > * See the vscode setting `"search.useIgnoreFiles"`.
116 > */
117 > useIgnoreFiles: boolean;
118 >
119 > /**
120 > * Whether symlinks should be followed while searching.
121 > * See the vscode setting `"search.followSymlinks"`.
122 > */
123 > followSymlinks: boolean;
124 >
125 > /**
126 > * Whether global files that exclude files, like .gitignore, should be respected.
127 > * See the vscode setting `"search.useGlobalIgnoreFiles"`.
128 > */
129 > useGlobalIgnoreFiles: boolean;
130 >
131 > /**
132 > * Whether files in parent directories that exclude files, like .gitignore, should be respected.
133 > * See the vscode setting `"search.useParentIgnoreFiles"`.
134 > */
135 > useParentIgnoreFiles: boolean;
136 > }
137 >
138 > /**
139 > * Options to specify the size of the result text preview.
140 > * These options don't affect the size of the match itself, just the amount of preview text.
141 > */
142 > export interface TextSearchPreviewOptions {
143 > /**
144 > * The maximum number of lines in the preview.
145 > * Only search providers that support multiline search will ever return more than one line in the match.
146 > */
147 > matchLines: number;
148 >
149 > /**
150 > * The maximum number of characters included per line.
151 > */
152 > charsPerLine: number;
153 > }
154 >
155 > /**
156 > * Options that apply to text search.
157 > */
158 > export interface TextSearchOptions extends SearchOptions {
159 > /**
160 > * The maximum number of results to be returned.
161 > */
162 > maxResults: number;
163 >
164 > /**
165 > * Options to specify the size of the result text preview.
166 > */
167 > previewOptions?: TextSearchPreviewOptions;
168 >
169 > /**
170 > * Exclude files larger than `maxFileSize` in bytes.
171 > */
172 > maxFileSize?: number;
173 >
174 > /**
175 > * Interpret files using this encoding.
176 > * See the vscode setting `"files.encoding"`
177 > */
178 > encoding?: string;
179 >
180 > /**
181 > * Number of lines of context to include before each match.
182 > */
183 > beforeContext?: number;
184 >
185 > /**
186 > * Number of lines of context to include after each match.
187 > */
188 > afterContext?: number;
189 > }
190 > /**
191 > * Options that apply to AI text search.
192 > */
193 > export interface AITextSearchOptions extends SearchOptions {
194 > /**
195 > * The maximum number of results to be returned.
196 > */
197 > maxResults: number;
198 >
199 > /**
200 > * Options to specify the size of the result text preview.
201 > */
202 > previewOptions?: TextSearchPreviewOptions;
203 >
204 > /**
205 > * Exclude files larger than `maxFileSize` in bytes.
206 > */
207 > maxFileSize?: number;
208 >
209 > /**
210 > * Number of lines of context to include before each match.
211 > */
212 > beforeContext?: number;
213 >
214 > /**
215 > * Number of lines of context to include after each match.
216 > */
217 > afterContext?: number;
218 > }
219 >
220 > /**
221 > * Information collected when text search is complete.
222 > */
223 > export interface TextSearchComplete {
224 > /**
225 > * Whether the search hit the limit on the maximum number of search results.
226 > * `maxResults` on [`TextSearchOptions`](#TextSearchOptions) specifies the max number of results.
227 > * - If exactly that number of matches exist, this should be false.
228 > * - If `maxResults` matches are returned and more exist, this should be true.
229 > * - If search hits an internal limit which is less than `maxResults`, this should be true.
230 > */
231 > limitHit?: boolean;
232 >
233 > /**
234 > * Additional information regarding the state of the completed search.
235 > *
236 > * Supports links in markdown syntax:
237 > * - Click to [run a command](command:workbench.action.OpenQuickPick)
238 > * - Click to [open a website](https://aka.ms)
239 > */
240 > message?: TextSearchCompleteMessage | TextSearchCompleteMessage[];
241 > }
242 >
243 > /**
244 > * The parameters of a query for file search.
245 > */
246 > export interface FileSearchQuery {
247 > /**
248 > * The search pattern to match against file paths.
249 > */
250 > pattern: string;
251 > }
252 >
253 > /**
254 > * Options that apply to file search.
255 > */
256 > export interface FileSearchOptions extends SearchOptions {
257 > /**
258 > * The maximum number of results to be returned.
259 > */
260 > maxResults?: number;
261 >
262 > /**
263 > * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache,
264 > * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared.
265 > */
266 > session?: CancellationToken;
267 > }
268 >
269 > /**
270 > * A preview of the text result.
271 > */
272 > export interface TextSearchMatchPreview {
273 > /**
274 > * The matching lines of text, or a portion of the matching line that contains the match.
275 > */
276 > text: string;
277 >
278 > /**
279 > * The Range within `text` corresponding to the text of the match.
280 > * The number of matches must match the TextSearchMatch's range property.
281 > */
282 > matches: Range | Range[];
283 > }
284 >
285 > /**
286 > * A match from a text search
287 > */
288 > export interface TextSearchMatch {
289 > /**
290 > * The uri for the matching document.
291 > */
292 > uri: URI;
293 >
294 > /**
295 > * The range of the match within the document, or multiple ranges for multiple matches.
296 > */
297 > ranges: Range | Range[];
298 >
299 > /**
300 > * A preview of the text match.
301 > */
302 > preview: TextSearchMatchPreview;
303 > }
304 >
305 > /**
306 > * Checks if the given object is of type TextSearchMatch.
307 > * @param object The object to check.
308 > * @returns True if the object is a TextSearchMatch, false otherwise.
309 > */
310 function isTextSearchMatch(object: any): object is TextSearchMatch {
311 return 'uri' in object && 'ranges' in object && 'preview' in object;
312 }
314 > /**
315 > * A line of context surrounding a TextSearchMatch.
316 > */
317 > export interface TextSearchContext {
318 > /**
319 > * The uri for the matching document.
320 > */
321 > uri: URI;
322 >
323 > /**
324 > * One line of text.
325 > * previewOptions.charsPerLine applies to this
326 > */
327 > text: string;
328 >
329 > /**
330 > * The line number of this line of context.
331 > */
332 > lineNumber: number;
333 > }
334 >
335 > export type TextSearchResult = TextSearchMatch | TextSearchContext;
336 >
337 > /**
338 > * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickaccess or other extensions.
339 > *
340 > * A FileSearchProvider is the more powerful of two ways to implement file search in VS Code. Use a FileSearchProvider if you wish to search within a folder for
341 > * all files that match the user's query.
342 > *
343 > * The FileSearchProvider will be invoked on every keypress in quickaccess. When `workspace.findFiles` is called, it will be invoked with an empty query string,
344 > * and in that case, every file in the folder should be returned.
345 > */
346 > export interface FileSearchProvider {
347 > /**
348 > * Provide the set of files that match a certain file path pattern.
349 > * @param query The parameters for this query.
350 > * @param options A set of options to consider while searching files.
351 > * @param progress A progress callback that must be invoked for all results.
352 > * @param token A cancellation token.
353 > */
354 > provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): ProviderResult<URI[]>;
355 > }
356 >
357 > /**
358 > * A TextSearchProvider provides search results for text results inside files in the workspace.
359 > */
360 > export interface TextSearchProvider {
361 > /**
362 > * Provide results that match the given text pattern.
363 > * @param query The parameters for this query.
364 > * @param options A set of options to consider while searching.
365 > * @param progress A progress callback that must be invoked for all results.
366 > * @param token A cancellation token.
367 > */
368 > provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: IProgress<TextSearchResult>, token: CancellationToken): ProviderResult<TextSearchComplete>;
369 > }
370 > /**
371 > * Options that can be set on a findTextInFiles search.
372 > */
373 > export interface FindTextInFilesOptions {
374 > /**
375 > * A [glob pattern](#GlobPattern) that defines the files to search for. The glob pattern
376 > * will be matched against the file paths of files relative to their workspace. Use a [relative pattern](#RelativePattern)
377 > * to restrict the search results to a [workspace folder](#WorkspaceFolder).
378 > */
379 > include?: GlobPattern;
380 >
381 > /**
382 > * A [glob pattern](#GlobPattern) that defines files and folders to exclude. The glob pattern
383 > * will be matched against the file paths of resulting matches relative to their workspace. When `undefined` only default excludes will
384 > * apply, when `null` no excludes will apply.
385 > */
386 > exclude?: GlobPattern | null;
387 >
388 > /**
389 > * The maximum number of results to search for
390 > */
391 > maxResults?: number;
392 >
393 > /**
394 > * Whether external files that exclude files, like .gitignore, should be respected.
395 > * See the vscode setting `"search.useIgnoreFiles"`.
396 > */
397 > useIgnoreFiles?: boolean;
398 >
399 > /**
400 > * Whether global files that exclude files, like .gitignore, should be respected.
401 > * See the vscode setting `"search.useGlobalIgnoreFiles"`.
402 > */
403 > useGlobalIgnoreFiles?: boolean;
404 >
405 > /**
406 > * Whether files in parent directories that exclude files, like .gitignore, should be respected.
407 > * See the vscode setting `"search.useParentIgnoreFiles"`.
408 > */
409 > useParentIgnoreFiles: boolean;
410 >
411 > /**
412 > * Whether symlinks should be followed while searching.
413 > * See the vscode setting `"search.followSymlinks"`.
414 > */
415 > followSymlinks?: boolean;
416 >
417 > /**
418 > * Interpret files using this encoding.
419 > * See the vscode setting `"files.encoding"`
420 > */
421 > encoding?: string;
422 >
423 > /**
424 > * Options to specify the size of the result text preview.
425 > */
426 > previewOptions?: TextSearchPreviewOptions;
427 >
428 > /**
429 > * Number of lines of context to include before each match.
430 > */
431 > beforeContext?: number;
432 >
433 > /**
434 > * Number of lines of context to include after each match.
435 > */
436 > afterContext?: number;
437 > }
438 >
439 function newToOldFileProviderOptions(options: FileSearchProviderOptions): FileSearchOptions[] {
440 return options.folderOptions.map(folderOption => ({
450 } satisfies FileSearchOptions));
451 }
453 > export class OldFileSearchProviderConverter implements FileSearchProvider2 {
454 > constructor(private provider: FileSearchProvider) { }
455 >
456 > provideFileSearchResults(pattern: string, options: FileSearchProviderOptions, token: CancellationToken): ProviderResult<URI[]> {
457 const getResult = async () => {
458 const newOpts = newToOldFileProviderOptions(options);
462 return getResult().then(e => coalesce(e).flat());
463 }
465 >
466 function newToOldTextProviderOptions(options: TextSearchProviderOptions): TextSearchOptions[] {
467 return options.folderOptions.map(folderOption => ({
481 } satisfies TextSearchOptions));
482 }
484 > export function newToOldPreviewOptions(options: {
485 matchLines?: number;
486 charsPerLine?: number;
495 };
496 }
498 > export function oldToNewTextSearchResult(result: TextSearchResult): TextSearchResult2 {
499 if (isTextSearchMatch(result)) {
500 const ranges = asArray(result.ranges).map((r, i) => {
508 }
509 }
511 > export class OldTextSearchProviderConverter implements TextSearchProvider2 {
512 > constructor(private provider: TextSearchProvider) { }
513 >
514 > provideTextSearchResults(query: TextSearchQuery2, options: TextSearchProviderOptions, progress: IProgress<TextSearchResult2>, token: CancellationToken): ProviderResult<TextSearchComplete2> {
515
516 const progressShim = (oldResult: TextSearchResult) => {
538 });
539 }
541 >
542 function validateProviderResult(result: TextSearchResult): boolean {
543 if (extensionResultIsMatch(result)) {
562 return true;
563 }
565 > export function extensionResultIsMatch(data: TextSearchResult): data is TextSearchMatch {
566 return !!(<TextSearchMatch>data).preview;
567 }
src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts 126 introduced LOC · 23 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- ripgrepTextSearchEngine.ts
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 = {
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,
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');
189 return undefined;
190 }
192 function buildRegexParseError(lines: string[]): string {
193 const errorMessage: string[] = ['Regex parse error'];
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) {
216 super();
217 this.stringDecoder = new StringDecoder();
218 }
220 > cancel(): void {
221 this.isDone = true;
222 }
224 > flush(): void {
225 this.handleDecodedData(this.stringDecoder.end());
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);
233 return this;
234 }
236 > handleData(data: Buffer | string): void {
237 if (this.isDone) {
238 return;
242 this.handleDecodedData(dataStr);
243 }
245 > private handleDecodedData(decodedData: string): void {
246 // check for newline before appending to remainder
247 let newlineIdx = decodedData.indexOf('\n');
267 this.remainder = dataStr.substring(prevIdx);
268 }
270 >
271 > private handleLine(outputLine: string): void {
272 if (this.isDone || !outputLine) {
273 return;
298 }
299 }
301 > private createTextSearchMatch(data: IRgMatch, uri: URI): TextSearchMatch2 {
302 const lineNumber = data.line_number - 1;
303 const fullText = bytesOrTextToString(data.lines);
365 internalResult.previewText);
366 }
368 > private createTextSearchContexts(data: IRgMatch, uri: URI): TextSearchContext2[] {
369 const text = bytesOrTextToString(data.lines);
370 const startLine = data.line_number;
374 .map((line, i) => new TextSearchContext2(uri, line, startLine + i));
375 }
377 > private onResult(match: TextSearchResult2): void {
378 this.emit('result', match);
379 }
381 >
382 function bytesOrTextToString(obj: any): string {
383 return obj.bytes ?
385 obj.text;
386 }
388 function getNumLinesAndLastNewlineLength(text: string): { numLines: number; lastLineLength: number } {
389 const re = /\n/g;
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'];
408 args.push(query.isCaseSensitive ? '--case-sensitive' : '--ignore-case');
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[] {
536 const globComponentWithBraceExpansion = performBraceExpansionForRipgrep(globComponent);
542
543 }
545 > export function unicodeEscapesToPCRE2(pattern: string): string {
546 // Match \u1234
547 const unicodePattern = /((?:[^\\]|^)(?:\\\\)*)\\u([a-z0-9]{4})/gi;
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
588 let re: ReAST.Pattern;
668 return output;
669 }
671 > export function fixNewline(pattern: string): string {
672 return pattern.replace(/\n/g, '\\r?\\n');
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 } {
684 let inBraces = false;
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);
764 if (fixedStart === undefined || fixedEnd === undefined) {