textModelSearch.ts ×26

Frontier kind: Code frontier

unlabeled · c_e6a4a4592221

927 tests · 7225 LOC · 34 files · introduces 0 tests · 132 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
33 ranges132 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
861 ranges7225 lines · 34 files · Browse complete extent
All tests (intent)
927 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: 132 introduced LOC across 33 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/editor/common/model/textModelSearch.ts 88 introduced LOC · 26 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- textModelSearch.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 { CharCode } from '../../../base/common/charCode.js';
7 > import * as strings from '../../../base/common/strings.js';
8 > import { WordCharacterClass, WordCharacterClassifier, getMapForWordSeparators } from '../core/wordCharacterClassifier.js';
9 > import { Position } from '../core/position.js';
10 > import { Range } from '../core/range.js';
11 > import { EndOfLinePreference, FindMatch, SearchData } from '../model.js';
12 > import { TextModel } from './textModel.js';
13 >
14 > const LIMIT_FIND_COUNT = 999;
15 >
16 > export class SearchParams {
17 > public readonly searchString: string;
18 > public readonly isRegex: boolean;
19 > public readonly matchCase: boolean;
20 > public readonly wordSeparators: string | null;
21 >
22 > constructor(searchString: string, isRegex: boolean, matchCase: boolean, wordSeparators: string | null) {
23 this.searchString = searchString;
24 this.isRegex = isRegex;
26 this.wordSeparators = wordSeparators;
27 }
29 > public parseSearchRequest(): SearchData | null {
30 if (this.searchString === '') {
31 return null;
65 return new SearchData(regex, this.wordSeparators ? getMapForWordSeparators(this.wordSeparators, []) : null, canUseSimpleSearch ? this.searchString : null);
66 }
68 >
69 > export function isMultilineRegexSource(searchString: string): boolean {
70 if (!searchString || searchString.length === 0) {
71 return false;
98 return false;
99 }
101 > export function createFindMatch(range: Range, rawMatches: RegExpExecArray, captureMatches: boolean): FindMatch {
102 if (!captureMatches) {
103 return new FindMatch(range, null);
109 return new FindMatch(range, matches);
110 }
112 > class LineFeedCounter {
113 >
114 > private readonly _lineFeedsOffsets: number[];
115 >
116 > constructor(text: string) {
117 const lineFeedsOffsets: number[] = [];
118 let lineFeedsOffsetsLen = 0;
124 this._lineFeedsOffsets = lineFeedsOffsets;
125 }
127 > public findLineFeedCountBeforeOffset(offset: number): number {
128 const lineFeedsOffsets = this._lineFeedsOffsets;
129 let min = 0;
157 return min + 1;
158 }
160 >
161 > export class TextModelSearch {
162 >
163 > public static findMatches(model: TextModel, searchParams: SearchParams, searchRange: Range, captureMatches: boolean, limitResultCount: number): FindMatch[] {
164 const searchData = searchParams.parseSearchRequest();
165 if (!searchData) {
172 return this._doFindMatchesLineByLine(model, searchRange, searchData, captureMatches, limitResultCount);
173 }
175 > /**
176 > * Multiline search always executes on the lines concatenated with \n.
177 > * We must therefore compensate for the count of \n in case the model is CRLF
178 > */
179 > private static _getMultilineMatchRange(model: TextModel, deltaOffset: number, text: string, lfCounter: LineFeedCounter | null, matchIndex: number, match0: string): Range {
180 let startOffset: number;
181 let lineFeedCountBeforeMatch = 0;
200 return new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);
201 }
203 > private static _doFindMatchesMultiline(model: TextModel, searchRange: Range, searcher: Searcher, captureMatches: boolean, limitResultCount: number): FindMatch[] {
204 const deltaOffset = model.getOffsetAt(searchRange.getStartPosition());
205 // We always execute multiline search over the lines joined with \n
223 return result;
224 }
226 > private static _doFindMatchesLineByLine(model: TextModel, searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[] {
227 const result: FindMatch[] = [];
228 let resultLen = 0;
252 return result;
253 }
255 > private static _findMatchesInLine(searchData: SearchData, text: string, lineNumber: number, deltaOffset: number, resultLen: number, result: FindMatch[], captureMatches: boolean, limitResultCount: number): number {
256 const wordSeparators = searchData.wordSeparators;
257 if (!captureMatches && searchData.simpleSearch) {
287 return resultLen;
288 }
290 > public static findNextMatch(model: TextModel, searchParams: SearchParams, searchStart: Position, captureMatches: boolean): FindMatch | null {
291 const searchData = searchParams.parseSearchRequest();
292 if (!searchData) {
301 return this._doFindNextMatchLineByLine(model, searchStart, searcher, captureMatches);
302 }
304 > private static _doFindNextMatchMultiline(model: TextModel, searchStart: Position, searcher: Searcher, captureMatches: boolean): FindMatch | null {
305 const searchTextStart = new Position(searchStart.lineNumber, 1);
306 const deltaOffset = model.getOffsetAt(searchTextStart);
328 return null;
329 }
331 > private static _doFindNextMatchLineByLine(model: TextModel, searchStart: Position, searcher: Searcher, captureMatches: boolean): FindMatch | null {
332 const lineCount = model.getLineCount();
333 const startLineNumber = searchStart.lineNumber;
351 return null;
352 }
354 > private static _findFirstMatchInLine(searcher: Searcher, text: string, lineNumber: number, fromColumn: number, captureMatches: boolean): FindMatch | null {
355 // Set regex to search from column
356 searcher.reset(fromColumn - 1);
365 return null;
366 }
368 > public static findPreviousMatch(model: TextModel, searchParams: SearchParams, searchStart: Position, captureMatches: boolean): FindMatch | null {
369 const searchData = searchParams.parseSearchRequest();
370 if (!searchData) {
379 return this._doFindPreviousMatchLineByLine(model, searchStart, searcher, captureMatches);
380 }
382 > private static _doFindPreviousMatchMultiline(model: TextModel, searchStart: Position, searcher: Searcher, captureMatches: boolean): FindMatch | null {
383 const matches = this._doFindMatchesMultiline(model, new Range(1, 1, searchStart.lineNumber, searchStart.column), searcher, captureMatches, 10 * LIMIT_FIND_COUNT);
384 if (matches.length > 0) {
394 return null;
395 }
397 > private static _doFindPreviousMatchLineByLine(model: TextModel, searchStart: Position, searcher: Searcher, captureMatches: boolean): FindMatch | null {
398 const lineCount = model.getLineCount();
399 const startLineNumber = searchStart.lineNumber;
417 return null;
418 }
420 > private static _findLastMatchInLine(searcher: Searcher, text: string, lineNumber: number, captureMatches: boolean): FindMatch | null {
421 let bestResult: FindMatch | null = null;
422 let m: RegExpExecArray | null;
427 return bestResult;
428 }
430 >
431 function leftIsWordBounday(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
432 if (matchStartIndex === 0) {
456 return false;
457 }
459 function rightIsWordBounday(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
460 if (matchStartIndex + matchLength === textLength) {
484 return false;
485 }
487 > export function isValidMatch(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
488 return (
489 leftIsWordBounday(wordSeparators, text, textLength, matchStartIndex, matchLength)
491 );
492 }
494 > export class Searcher {
495 > public readonly _wordSeparators: WordCharacterClassifier | null;
496 > private readonly _searchRegex: RegExp;
497 > private _prevMatchStartIndex: number;
498 > private _prevMatchLength: number;
499 >
500 > constructor(wordSeparators: WordCharacterClassifier | null, searchRegex: RegExp,) {
501 this._wordSeparators = wordSeparators;
502 this._searchRegex = searchRegex;
504 this._prevMatchLength = 0;
505 }
507 > public reset(lastIndex: number): void {
508 this._searchRegex.lastIndex = lastIndex;
509 this._prevMatchStartIndex = -1;
510 this._prevMatchLength = 0;
511 }
513 > public next(text: string): RegExpExecArray | null {
514 const textLength = text.length;
515
src/vs/editor/common/core/wordCharacterClassifier.ts 44 introduced LOC · 7 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- wordCharacterClassifier.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 { CharCode } from '../../../base/common/charCode.js';
7 > import { safeIntl } from '../../../base/common/date.js';
8 > import { Lazy } from '../../../base/common/lazy.js';
9 > import { LRUCache } from '../../../base/common/map.js';
10 > import { CharacterClassifier } from './characterClassifier.js';
11 >
12 > export const enum WordCharacterClass {
13 > Regular = 0,
14 > Whitespace = 1,
15 > WordSeparator = 2
16 > }
17 >
18 > export class WordCharacterClassifier extends CharacterClassifier<WordCharacterClass> {
19 >
20 > public readonly intlSegmenterLocales: Intl.UnicodeBCP47LocaleIdentifier[];
21 > private readonly _segmenter: Lazy<Intl.Segmenter> | null = null;
22 > private _cachedLine: string | null = null;
23 > private _cachedSegments: IntlWordSegmentData[] = [];
24 >
25 > constructor(wordSeparators: string, intlSegmenterLocales: Intl.UnicodeBCP47LocaleIdentifier[]) {
26 super(WordCharacterClass.Regular);
27 this.intlSegmenterLocales = intlSegmenterLocales;
39 this.set(CharCode.Tab, WordCharacterClass.Whitespace);
40 }
42 > public findPrevIntlWordBeforeOrAtOffset(line: string, offset: number): IntlWordSegmentData | null {
43 let candidate: IntlWordSegmentData | null = null;
44 for (const segment of this._getIntlSegmenterWordsOnLine(line)) {
50 return candidate;
51 }
53 > public findNextIntlWordAtOrAfterOffset(lineContent: string, offset: number): IntlWordSegmentData | null {
54 for (const segment of this._getIntlSegmenterWordsOnLine(lineContent)) {
55 if (segment.index < offset) {
60 return null;
61 }
63 > private _getIntlSegmenterWordsOnLine(line: string): IntlWordSegmentData[] {
64 if (!this._segmenter) {
65 return [];
77 return this._cachedSegments;
78 }
80 > private _filterWordSegments(segments: Intl.Segments): IntlWordSegmentData[] {
81 const result: IntlWordSegmentData[] = [];
82 for (const segment of segments) {
87 return result;
88 }
90 > private _isWordLike(segment: Intl.SegmentData): segment is IntlWordSegmentData {
91 if (segment.isWordLike) {
92 return true;
94 return false;
95 }
97 >
98 > export interface IntlWordSegmentData extends Intl.SegmentData {
99 > isWordLike: true;
100 > }
101 >
102 > const wordClassifierCache = new LRUCache<string, WordCharacterClassifier>(10);
103 >
104 > export function getMapForWordSeparators(wordSeparators: string, intlSegmenterLocales: Intl.UnicodeBCP47LocaleIdentifier[]): WordCharacterClassifier {
105 const key = `${wordSeparators}/${intlSegmenterLocales.join(',')}`;
106 let result = wordClassifierCache.get(key)!;