Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/common/model/textModelSearch.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
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 assert from 'assert';
7
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
8
import { Position } from '../../../common/core/position.js';
9
import { Range } from '../../../common/core/range.js';
10
import { getMapForWordSeparators } from '../../../common/core/wordCharacterClassifier.js';
11
import { USUAL_WORD_SEPARATORS } from '../../../common/core/wordHelper.js';
12
import { EndOfLineSequence, FindMatch, SearchData } from '../../../common/model.js';
13
import { TextModel } from '../../../common/model/textModel.js';
14
import { SearchParams, TextModelSearch, isMultilineRegexSource } from '../../../common/model/textModelSearch.js';
15
import { createTextModel } from '../testTextModel.js';
16
17
// --------- Find
18
suite('TextModelSearch', () => {
19
20
ensureNoDisposablesAreLeakedInTestSuite();
21
22
const usualWordSeparators = getMapForWordSeparators(USUAL_WORD_SEPARATORS, []);
23
24
function assertFindMatch(actual: FindMatch | null, expectedRange: Range, expectedMatches: string[] | null = null): void {
25
assert.deepStrictEqual(actual, new FindMatch(expectedRange, expectedMatches));
26
}
27
28
function _assertFindMatches(model: TextModel, searchParams: SearchParams, expectedMatches: FindMatch[]): void {
29
const actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), false, 1000);
30
assert.deepStrictEqual(actual, expectedMatches, 'findMatches OK');
31
32
// test `findNextMatch`
33
let startPos = new Position(1, 1);
34
let match = TextModelSearch.findNextMatch(model, searchParams, startPos, false);
35
assert.deepStrictEqual(match, expectedMatches[0], `findNextMatch ${startPos}`);
36
for (const expectedMatch of expectedMatches) {
37
startPos = expectedMatch.range.getStartPosition();
38
match = TextModelSearch.findNextMatch(model, searchParams, startPos, false);
39
assert.deepStrictEqual(match, expectedMatch, `findNextMatch ${startPos}`);
40
}
41
42
// test `findPrevMatch`
43
startPos = new Position(model.getLineCount(), model.getLineMaxColumn(model.getLineCount()));
44
match = TextModelSearch.findPreviousMatch(model, searchParams, startPos, false);
45
assert.deepStrictEqual(match, expectedMatches[expectedMatches.length - 1], `findPrevMatch ${startPos}`);
46
for (const expectedMatch of expectedMatches) {
47
startPos = expectedMatch.range.getEndPosition();
48
match = TextModelSearch.findPreviousMatch(model, searchParams, startPos, false);
49
assert.deepStrictEqual(match, expectedMatch, `findPrevMatch ${startPos}`);
50
}
51
}
52
53
function assertFindMatches(text: string, searchString: string, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, _expected: [number, number, number, number][]): void {
54
const expectedRanges = _expected.map(entry => new Range(entry[0], entry[1], entry[2], entry[3]));
55
const expectedMatches = expectedRanges.map(entry => new FindMatch(entry, null));
56
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
57
58
const model = createTextModel(text);
59
_assertFindMatches(model, searchParams, expectedMatches);
60
model.dispose();
61
62
63
const model2 = createTextModel(text);
64
model2.setEOL(EndOfLineSequence.CRLF);
65
_assertFindMatches(model2, searchParams, expectedMatches);
66
model2.dispose();
67
}
68
69
const regularText = [
70
'This is some foo - bar text which contains foo and bar - as in Barcelona.',
71
'Now it begins a word fooBar and now it is caps Foo-isn\'t this great?',
72
'And here\'s a dull line with nothing interesting in it',
73
'It is also interesting if it\'s part of a word like amazingFooBar',
74
'Again nothing interesting here'
75
];
76
77
test('Simple find', () => {
78
assertFindMatches(
79
regularText.join('\n'),
80
'foo', false, false, null,
81
[
82
[1, 14, 1, 17],
83
[1, 44, 1, 47],
84
[2, 22, 2, 25],
85
[2, 48, 2, 51],
86
[4, 59, 4, 62]
87
]
88
);
89
});
90
91
test('Case sensitive find', () => {
92
assertFindMatches(
93
regularText.join('\n'),
94
'foo', false, true, null,
95
[
96
[1, 14, 1, 17],
97
[1, 44, 1, 47],
98
[2, 22, 2, 25]
99
]
100
);
101
});
102
103
test('Whole words find', () => {
104
assertFindMatches(
105
regularText.join('\n'),
106
'foo', false, false, USUAL_WORD_SEPARATORS,
107
[
108
[1, 14, 1, 17],
109
[1, 44, 1, 47],
110
[2, 48, 2, 51]
111
]
112
);
113
});
114
115
test('/^/ find', () => {
116
assertFindMatches(
117
regularText.join('\n'),
118
'^', true, false, null,
119
[
120
[1, 1, 1, 1],
121
[2, 1, 2, 1],
122
[3, 1, 3, 1],
123
[4, 1, 4, 1],
124
[5, 1, 5, 1]
125
]
126
);
127
});
128
129
test('/$/ find', () => {
130
assertFindMatches(
131
regularText.join('\n'),
132
'$', true, false, null,
133
[
134
[1, 74, 1, 74],
135
[2, 69, 2, 69],
136
[3, 54, 3, 54],
137
[4, 65, 4, 65],
138
[5, 31, 5, 31]
139
]
140
);
141
});
142
143
test('/.*/ find', () => {
144
assertFindMatches(
145
regularText.join('\n'),
146
'.*', true, false, null,
147
[
148
[1, 1, 1, 74],
149
[2, 1, 2, 69],
150
[3, 1, 3, 54],
151
[4, 1, 4, 65],
152
[5, 1, 5, 31]
153
]
154
);
155
});
156
157
test('/^$/ find', () => {
158
assertFindMatches(
159
[
160
'This is some foo - bar text which contains foo and bar - as in Barcelona.',
161
'',
162
'And here\'s a dull line with nothing interesting in it',
163
'',
164
'Again nothing interesting here'
165
].join('\n'),
166
'^$', true, false, null,
167
[
168
[2, 1, 2, 1],
169
[4, 1, 4, 1]
170
]
171
);
172
});
173
174
test('multiline find 1', () => {
175
assertFindMatches(
176
[
177
'Just some text text',
178
'Just some text text',
179
'some text again',
180
'again some text'
181
].join('\n'),
182
'text\\n', true, false, null,
183
[
184
[1, 16, 2, 1],
185
[2, 16, 3, 1],
186
]
187
);
188
});
189
190
test('multiline find 2', () => {
191
assertFindMatches(
192
[
193
'Just some text text',
194
'Just some text text',
195
'some text again',
196
'again some text'
197
].join('\n'),
198
'text\\nJust', true, false, null,
199
[
200
[1, 16, 2, 5]
201
]
202
);
203
});
204
205
test('multiline find 3', () => {
206
assertFindMatches(
207
[
208
'Just some text text',
209
'Just some text text',
210
'some text again',
211
'again some text'
212
].join('\n'),
213
'\\nagain', true, false, null,
214
[
215
[3, 16, 4, 6]
216
]
217
);
218
});
219
220
test('multiline find 4', () => {
221
assertFindMatches(
222
[
223
'Just some text text',
224
'Just some text text',
225
'some text again',
226
'again some text'
227
].join('\n'),
228
'.*\\nJust.*\\n', true, false, null,
229
[
230
[1, 1, 3, 1]
231
]
232
);
233
});
234
235
test('multiline find with line beginning regex', () => {
236
assertFindMatches(
237
[
238
'if',
239
'else',
240
'',
241
'if',
242
'else'
243
].join('\n'),
244
'^if\\nelse', true, false, null,
245
[
246
[1, 1, 2, 5],
247
[4, 1, 5, 5]
248
]
249
);
250
});
251
252
test('matching empty lines using boundary expression', () => {
253
assertFindMatches(
254
[
255
'if',
256
'',
257
'else',
258
' ',
259
'if',
260
' ',
261
'else'
262
].join('\n'),
263
'^\\s*$\\n', true, false, null,
264
[
265
[2, 1, 3, 1],
266
[4, 1, 5, 1],
267
[6, 1, 7, 1]
268
]
269
);
270
});
271
272
test('matching lines starting with A and ending with B', () => {
273
assertFindMatches(
274
[
275
'a if b',
276
'a',
277
'ab',
278
'eb'
279
].join('\n'),
280
'^a.*b$', true, false, null,
281
[
282
[1, 1, 1, 7],
283
[3, 1, 3, 3]
284
]
285
);
286
});
287
288
test('multiline find with line ending regex', () => {
289
assertFindMatches(
290
[
291
'if',
292
'else',
293
'',
294
'if',
295
'elseif',
296
'else'
297
].join('\n'),
298
'if\\nelse$', true, false, null,
299
[
300
[1, 1, 2, 5],
301
[5, 5, 6, 5]
302
]
303
);
304
});
305
306
test('issue #4836 - ^.*$', () => {
307
assertFindMatches(
308
[
309
'Just some text text',
310
'',
311
'some text again',
312
'',
313
'again some text'
314
].join('\n'),
315
'^.*$', true, false, null,
316
[
317
[1, 1, 1, 20],
318
[2, 1, 2, 1],
319
[3, 1, 3, 16],
320
[4, 1, 4, 1],
321
[5, 1, 5, 16],
322
]
323
);
324
});
325
326
test('multiline find for non-regex string', () => {
327
assertFindMatches(
328
[
329
'Just some text text',
330
'some text text',
331
'some text again',
332
'again some text',
333
'but not some'
334
].join('\n'),
335
'text\nsome', false, false, null,
336
[
337
[1, 16, 2, 5],
338
[2, 11, 3, 5],
339
]
340
);
341
});
342
343
test('issue #3623: Match whole word does not work for not latin characters', () => {
344
assertFindMatches(
345
[
346
'я',
347
'компилятор',
348
'обфускация',
349
':я-я'
350
].join('\n'),
351
'я', false, false, USUAL_WORD_SEPARATORS,
352
[
353
[1, 1, 1, 2],
354
[4, 2, 4, 3],
355
[4, 4, 4, 5],
356
]
357
);
358
});
359
360
test('issue #27459: Match whole words regression', () => {
361
assertFindMatches(
362
[
363
'this._register(this._textAreaInput.onKeyDown((e: IKeyboardEvent) => {',
364
' this._viewController.emitKeyDown(e);',
365
'}));',
366
].join('\n'),
367
'((e: ', false, false, USUAL_WORD_SEPARATORS,
368
[
369
[1, 45, 1, 50]
370
]
371
);
372
});
373
374
test('issue #27594: Search results disappear', () => {
375
assertFindMatches(
376
[
377
'this.server.listen(0);',
378
].join('\n'),
379
'listen(', false, false, USUAL_WORD_SEPARATORS,
380
[
381
[1, 13, 1, 20]
382
]
383
);
384
});
385
386
test('findNextMatch without regex', () => {
387
const model = createTextModel('line line one\nline two\nthree');
388
389
const searchParams = new SearchParams('line', false, false, null);
390
391
let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), false);
392
assertFindMatch(actual, new Range(1, 1, 1, 5));
393
394
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
395
assertFindMatch(actual, new Range(1, 6, 1, 10));
396
397
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 3), false);
398
assertFindMatch(actual, new Range(1, 6, 1, 10));
399
400
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
401
assertFindMatch(actual, new Range(2, 1, 2, 5));
402
403
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
404
assertFindMatch(actual, new Range(1, 1, 1, 5));
405
406
model.dispose();
407
});
408
409
test('findNextMatch with beginning boundary regex', () => {
410
const model = createTextModel('line one\nline two\nthree');
411
412
const searchParams = new SearchParams('^line', true, false, null);
413
414
let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), false);
415
assertFindMatch(actual, new Range(1, 1, 1, 5));
416
417
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
418
assertFindMatch(actual, new Range(2, 1, 2, 5));
419
420
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 3), false);
421
assertFindMatch(actual, new Range(2, 1, 2, 5));
422
423
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
424
assertFindMatch(actual, new Range(1, 1, 1, 5));
425
426
model.dispose();
427
});
428
429
test('findNextMatch with beginning boundary regex and line has repetitive beginnings', () => {
430
const model = createTextModel('line line one\nline two\nthree');
431
432
const searchParams = new SearchParams('^line', true, false, null);
433
434
let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), false);
435
assertFindMatch(actual, new Range(1, 1, 1, 5));
436
437
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
438
assertFindMatch(actual, new Range(2, 1, 2, 5));
439
440
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 3), false);
441
assertFindMatch(actual, new Range(2, 1, 2, 5));
442
443
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
444
assertFindMatch(actual, new Range(1, 1, 1, 5));
445
446
model.dispose();
447
});
448
449
test('findNextMatch with beginning boundary multiline regex and line has repetitive beginnings', () => {
450
const model = createTextModel('line line one\nline two\nline three\nline four');
451
452
const searchParams = new SearchParams('^line.*\\nline', true, false, null);
453
454
let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), false);
455
assertFindMatch(actual, new Range(1, 1, 2, 5));
456
457
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
458
assertFindMatch(actual, new Range(3, 1, 4, 5));
459
460
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(2, 1), false);
461
assertFindMatch(actual, new Range(2, 1, 3, 5));
462
463
model.dispose();
464
});
465
466
test('findNextMatch with ending boundary regex', () => {
467
const model = createTextModel('one line line\ntwo line\nthree');
468
469
const searchParams = new SearchParams('line$', true, false, null);
470
471
let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), false);
472
assertFindMatch(actual, new Range(1, 10, 1, 14));
473
474
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 4), false);
475
assertFindMatch(actual, new Range(1, 10, 1, 14));
476
477
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
478
assertFindMatch(actual, new Range(2, 5, 2, 9));
479
480
actual = TextModelSearch.findNextMatch(model, searchParams, actual!.range.getEndPosition(), false);
481
assertFindMatch(actual, new Range(1, 10, 1, 14));
482
483
model.dispose();
484
});
485
486
test('findMatches with capturing matches', () => {
487
const model = createTextModel('one line line\ntwo line\nthree');
488
489
const searchParams = new SearchParams('(l(in)e)', true, false, null);
490
491
const actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100);
492
assert.deepStrictEqual(actual, [
493
new FindMatch(new Range(1, 5, 1, 9), ['line', 'line', 'in']),
494
new FindMatch(new Range(1, 10, 1, 14), ['line', 'line', 'in']),
495
new FindMatch(new Range(2, 5, 2, 9), ['line', 'line', 'in']),
496
]);
497
498
model.dispose();
499
});
500
501
test('findMatches multiline with capturing matches', () => {
502
const model = createTextModel('one line line\ntwo line\nthree');
503
504
const searchParams = new SearchParams('(l(in)e)\\n', true, false, null);
505
506
const actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100);
507
assert.deepStrictEqual(actual, [
508
new FindMatch(new Range(1, 10, 2, 1), ['line\n', 'line', 'in']),
509
new FindMatch(new Range(2, 5, 3, 1), ['line\n', 'line', 'in']),
510
]);
511
512
model.dispose();
513
});
514
515
test('findNextMatch with capturing matches', () => {
516
const model = createTextModel('one line line\ntwo line\nthree');
517
518
const searchParams = new SearchParams('(l(in)e)', true, false, null);
519
520
const actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
521
assertFindMatch(actual, new Range(1, 5, 1, 9), ['line', 'line', 'in']);
522
523
model.dispose();
524
});
525
526
test('findNextMatch multiline with capturing matches', () => {
527
const model = createTextModel('one line line\ntwo line\nthree');
528
529
const searchParams = new SearchParams('(l(in)e)\\n', true, false, null);
530
531
const actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
532
assertFindMatch(actual, new Range(1, 10, 2, 1), ['line\n', 'line', 'in']);
533
534
model.dispose();
535
});
536
537
test('findPreviousMatch with capturing matches', () => {
538
const model = createTextModel('one line line\ntwo line\nthree');
539
540
const searchParams = new SearchParams('(l(in)e)', true, false, null);
541
542
const actual = TextModelSearch.findPreviousMatch(model, searchParams, new Position(1, 1), true);
543
assertFindMatch(actual, new Range(2, 5, 2, 9), ['line', 'line', 'in']);
544
545
model.dispose();
546
});
547
548
test('findPreviousMatch multiline with capturing matches', () => {
549
const model = createTextModel('one line line\ntwo line\nthree');
550
551
const searchParams = new SearchParams('(l(in)e)\\n', true, false, null);
552
553
const actual = TextModelSearch.findPreviousMatch(model, searchParams, new Position(1, 1), true);
554
assertFindMatch(actual, new Range(2, 5, 3, 1), ['line\n', 'line', 'in']);
555
556
model.dispose();
557
});
558
559
test('\\n matches \\r\\n', () => {
560
const model = createTextModel('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni');
561
562
assert.strictEqual(model.getEOL(), '\r\n');
563
564
let searchParams = new SearchParams('h\\n', true, false, null);
565
let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
566
actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000)[0];
567
assertFindMatch(actual, new Range(8, 1, 9, 1), ['h\n']);
568
569
searchParams = new SearchParams('g\\nh\\n', true, false, null);
570
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
571
actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000)[0];
572
assertFindMatch(actual, new Range(7, 1, 9, 1), ['g\nh\n']);
573
574
searchParams = new SearchParams('\\ni', true, false, null);
575
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
576
actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000)[0];
577
assertFindMatch(actual, new Range(8, 2, 9, 2), ['\ni']);
578
579
model.dispose();
580
});
581
582
test('\\r can never be found', () => {
583
const model = createTextModel('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni');
584
585
assert.strictEqual(model.getEOL(), '\r\n');
586
587
const searchParams = new SearchParams('\\r\\n', true, false, null);
588
const actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
589
assert.strictEqual(actual, null);
590
assert.deepStrictEqual(TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000), []);
591
592
model.dispose();
593
});
594
595
function assertParseSearchResult(searchString: string, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, expected: SearchData | null): void {
596
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
597
const actual = searchParams.parseSearchRequest();
598
599
if (expected === null) {
600
assert.ok(actual === null);
601
} else {
602
assert.deepStrictEqual(actual!.regex, expected.regex);
603
assert.deepStrictEqual(actual!.simpleSearch, expected.simpleSearch);
604
if (wordSeparators) {
605
assert.ok(actual!.wordSeparators !== null);
606
} else {
607
assert.ok(actual!.wordSeparators === null);
608
}
609
}
610
}
611
612
test('parseSearchRequest invalid', () => {
613
assertParseSearchResult('', true, true, USUAL_WORD_SEPARATORS, null);
614
assertParseSearchResult('(', true, false, null, null);
615
});
616
617
test('parseSearchRequest non regex', () => {
618
assertParseSearchResult('foo', false, false, null, new SearchData(/foo/giu, null, null));
619
assertParseSearchResult('foo', false, false, USUAL_WORD_SEPARATORS, new SearchData(/foo/giu, usualWordSeparators, null));
620
assertParseSearchResult('foo', false, true, null, new SearchData(/foo/gu, null, 'foo'));
621
assertParseSearchResult('foo', false, true, USUAL_WORD_SEPARATORS, new SearchData(/foo/gu, usualWordSeparators, 'foo'));
622
assertParseSearchResult('foo\\n', false, false, null, new SearchData(/foo\\n/giu, null, null));
623
assertParseSearchResult('foo\\\\n', false, false, null, new SearchData(/foo\\\\n/giu, null, null));
624
assertParseSearchResult('foo\\r', false, false, null, new SearchData(/foo\\r/giu, null, null));
625
assertParseSearchResult('foo\\\\r', false, false, null, new SearchData(/foo\\\\r/giu, null, null));
626
});
627
628
test('parseSearchRequest regex', () => {
629
assertParseSearchResult('foo', true, false, null, new SearchData(/foo/giu, null, null));
630
assertParseSearchResult('foo', true, false, USUAL_WORD_SEPARATORS, new SearchData(/foo/giu, usualWordSeparators, null));
631
assertParseSearchResult('foo', true, true, null, new SearchData(/foo/gu, null, null));
632
assertParseSearchResult('foo', true, true, USUAL_WORD_SEPARATORS, new SearchData(/foo/gu, usualWordSeparators, null));
633
assertParseSearchResult('foo\\n', true, false, null, new SearchData(/foo\n/gimu, null, null));
634
assertParseSearchResult('foo\\\\n', true, false, null, new SearchData(/foo\\n/giu, null, null));
635
assertParseSearchResult('foo\\r', true, false, null, new SearchData(/foo\r/gimu, null, null));
636
assertParseSearchResult('foo\\\\r', true, false, null, new SearchData(/foo\\r/giu, null, null));
637
});
638
639
test('issue #53415. \W should match line break.', () => {
640
assertFindMatches(
641
[
642
'text',
643
'180702-',
644
'180703-180704'
645
].join('\n'),
646
'\\d{6}-\\W', true, false, null,
647
[
648
[2, 1, 3, 1]
649
]
650
);
651
652
assertFindMatches(
653
[
654
'Just some text',
655
'',
656
'Just'
657
].join('\n'),
658
'\\W', true, false, null,
659
[
660
[1, 5, 1, 6],
661
[1, 10, 1, 11],
662
[1, 15, 2, 1],
663
[2, 1, 3, 1]
664
]
665
);
666
667
// Line break doesn't affect the result as we always use \n as line break when doing search
668
assertFindMatches(
669
[
670
'Just some text',
671
'',
672
'Just'
673
].join('\r\n'),
674
'\\W', true, false, null,
675
[
676
[1, 5, 1, 6],
677
[1, 10, 1, 11],
678
[1, 15, 2, 1],
679
[2, 1, 3, 1]
680
]
681
);
682
683
assertFindMatches(
684
[
685
'Just some text',
686
'\tJust',
687
'Just'
688
].join('\n'),
689
'\\W', true, false, null,
690
[
691
[1, 5, 1, 6],
692
[1, 10, 1, 11],
693
[1, 15, 2, 1],
694
[2, 1, 2, 2],
695
[2, 6, 3, 1],
696
]
697
);
698
699
// line break is seen as one non-word character
700
assertFindMatches(
701
[
702
'Just some text',
703
'',
704
'Just'
705
].join('\n'),
706
'\\W{2}', true, false, null,
707
[
708
[1, 5, 1, 7],
709
[1, 16, 3, 1]
710
]
711
);
712
713
// even if it's \r\n
714
assertFindMatches(
715
[
716
'Just some text',
717
'',
718
'Just'
719
].join('\r\n'),
720
'\\W{2}', true, false, null,
721
[
722
[1, 5, 1, 7],
723
[1, 16, 3, 1]
724
]
725
);
726
});
727
728
test('Simple find using unicode escape sequences', () => {
729
assertFindMatches(
730
regularText.join('\n'),
731
'\\u{0066}\\u006f\\u006F', true, false, null,
732
[
733
[1, 14, 1, 17],
734
[1, 44, 1, 47],
735
[2, 22, 2, 25],
736
[2, 48, 2, 51],
737
[4, 59, 4, 62]
738
]
739
);
740
});
741
742
test('isMultilineRegexSource', () => {
743
assert(!isMultilineRegexSource('foo'));
744
assert(!isMultilineRegexSource(''));
745
assert(!isMultilineRegexSource('foo\\sbar'));
746
assert(!isMultilineRegexSource('\\\\notnewline'));
747
748
assert(isMultilineRegexSource('foo\\nbar'));
749
assert(isMultilineRegexSource('foo\\nbar\\s'));
750
assert(isMultilineRegexSource('foo\\r\\n'));
751
assert(isMultilineRegexSource('\\n'));
752
assert(isMultilineRegexSource('foo\\W'));
753
assert(isMultilineRegexSource('foo\n'));
754
assert(isMultilineRegexSource('foo\r\n'));
755
});
756
757
test('isMultilineRegexSource correctly identifies multiline patterns', () => {
758
const singleLinePatterns = [
759
'MARK:\\s*(?<label>.*)$',
760
'^// Header$',
761
'\\s*[-=]+\\s*',
762
];
763
764
const multiLinePatterns = [
765
'^\/\/ =+\\n^\/\/ (?<label>[^\\n]+?)\\n^\/\/ =+$',
766
'header\\r\\nfooter',
767
'start\\r|\\nend',
768
'top\nmiddle\r\nbottom'
769
];
770
771
for (const pattern of singleLinePatterns) {
772
assert.strictEqual(isMultilineRegexSource(pattern), false, `Pattern should not be multiline: ${pattern}`);
773
}
774
775
for (const pattern of multiLinePatterns) {
776
assert.strictEqual(isMultilineRegexSource(pattern), true, `Pattern should be multiline: ${pattern}`);
777
}
778
});
779
780
test('issue #74715. \\d* finds empty string and stops searching.', () => {
781
const model = createTextModel('10.243.30.10');
782
783
const searchParams = new SearchParams('\\d*', true, false, null);
784
785
const actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100);
786
assert.deepStrictEqual(actual, [
787
new FindMatch(new Range(1, 1, 1, 3), ['10']),
788
new FindMatch(new Range(1, 3, 1, 3), ['']),
789
new FindMatch(new Range(1, 4, 1, 7), ['243']),
790
new FindMatch(new Range(1, 7, 1, 7), ['']),
791
new FindMatch(new Range(1, 8, 1, 10), ['30']),
792
new FindMatch(new Range(1, 10, 1, 10), ['']),
793
new FindMatch(new Range(1, 11, 1, 13), ['10'])
794
]);
795
796
model.dispose();
797
});
798
799
test('issue #100134. Zero-length matches should properly step over surrogate pairs', () => {
800
// 1[Laptop]1 - there shoud be no matches inside of [Laptop] emoji
801
assertFindMatches('1\uD83D\uDCBB1', '()', true, false, null,
802
[
803
[1, 1, 1, 1],
804
[1, 2, 1, 2],
805
[1, 4, 1, 4],
806
[1, 5, 1, 5],
807
808
]
809
);
810
// 1[Hacker Cat]1 = 1[Cat Face][ZWJ][Laptop]1 - there shoud be matches between emoji and ZWJ
811
// there shoud be no matches inside of [Cat Face] and [Laptop] emoji
812
assertFindMatches('1\uD83D\uDC31\u200D\uD83D\uDCBB1', '()', true, false, null,
813
[
814
[1, 1, 1, 1],
815
[1, 2, 1, 2],
816
[1, 4, 1, 4],
817
[1, 5, 1, 5],
818
[1, 7, 1, 7],
819
[1, 8, 1, 8]
820
]
821
);
822
});
823
});
824
825