Skip to content

Commit c6b3f39

Browse files
committed
1 parent 97ddcf6 commit c6b3f39

8 files changed

Lines changed: 219 additions & 133 deletions

File tree

.vscode/launch.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"stopOnEntry": false,
1010
"args": [
1111
"--timeout",
12-
"999999"
12+
"999999",
13+
"-g",
14+
"Editor Model - Find"
1315
],
1416
"cwd": "${workspaceRoot}",
1517
"runtimeArgs": [],

src/vs/base/common/strings.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,21 @@ export function endsWith(haystack: string, needle: string): boolean {
174174
}
175175
}
176176

177-
export function createRegExp(searchString: string, isRegex: boolean, matchCase: boolean, wholeWord: boolean, global:boolean): RegExp {
177+
export interface RegExpOptions {
178+
matchCase?: boolean;
179+
wholeWord?: boolean;
180+
multiline?: boolean;
181+
global?: boolean;
182+
}
183+
184+
export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp {
178185
if (searchString === '') {
179186
throw new Error('Cannot create regex from empty string');
180187
}
181188
if (!isRegex) {
182189
searchString = searchString.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&');
183190
}
184-
if (wholeWord) {
191+
if (options.wholeWord) {
185192
if (!/\B/.test(searchString.charAt(0))) {
186193
searchString = '\\b' + searchString;
187194
}
@@ -190,12 +197,15 @@ export function createRegExp(searchString: string, isRegex: boolean, matchCase:
190197
}
191198
}
192199
let modifiers = '';
193-
if (global) {
200+
if (options.global) {
194201
modifiers += 'g';
195202
}
196-
if (!matchCase) {
203+
if (!options.matchCase) {
197204
modifiers += 'i';
198205
}
206+
if (options.multiline) {
207+
modifiers += 'm';
208+
}
199209

200210
return new RegExp(searchString, modifiers);
201211
}

src/vs/editor/common/model/textModel.ts

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ import {IndentRange, computeRanges} from 'vs/editor/common/model/indentRanges';
1818
const LIMIT_FIND_COUNT = 999;
1919
export const LONG_LINE_BOUNDARY = 1000;
2020

21-
export interface IParsedSearchRequest {
22-
regex: RegExp;
23-
isMultiline: boolean;
24-
}
25-
2621
export class TextModel extends OrderGuaranteeEventEmitter implements editorCommon.ITextModel {
2722
private static MODEL_SYNC_LIMIT = 5 * 1024 * 1024; // 5 MB
2823
private static MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB
@@ -764,15 +759,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
764759
return false;
765760
}
766761

767-
public static parseSearchRequest(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean): IParsedSearchRequest {
762+
public static parseSearchRequest(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean): RegExp {
768763
if (searchString === '') {
769764
return null;
770765
}
771766

772767
// Try to create a RegExp out of the params
773-
var regex:RegExp = null;
768+
var regex: RegExp = null;
769+
var multiline = isRegex && TextModel._isMultiline(searchString);
774770
try {
775-
regex = strings.createRegExp(searchString, isRegex, matchCase, wholeWord, true);
771+
regex = strings.createRegExp(searchString, isRegex, {matchCase, wholeWord, multiline, global: true});
776772
} catch (err) {
777773
return null;
778774
}
@@ -781,15 +777,12 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
781777
return null;
782778
}
783779

784-
return {
785-
regex: regex,
786-
isMultiline: isRegex && TextModel._isMultiline(searchString)
787-
};
780+
return regex;
788781
}
789782

790783
public findMatches(searchString:string, rawSearchScope:any, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount:number = LIMIT_FIND_COUNT): Range[] {
791-
let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
792-
if (!r) {
784+
let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
785+
if (!regex) {
793786
return [];
794787
}
795788

@@ -800,10 +793,10 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
800793
searchRange = this.getFullModelRange();
801794
}
802795

803-
if (r.isMultiline) {
804-
return this._doFindMatchesMultiline(searchRange, r.regex, limitResultCount);
796+
if (regex.multiline) {
797+
return this._doFindMatchesMultiline(searchRange, regex, limitResultCount);
805798
}
806-
return this._doFindMatchesLineByLine(searchRange, r.regex, limitResultCount);
799+
return this._doFindMatchesLineByLine(searchRange, regex, limitResultCount);
807800
}
808801

809802
private _doFindMatchesMultiline(searchRange:Range, searchRegex:RegExp, limitResultCount:number): Range[] {
@@ -871,16 +864,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
871864
}
872865

873866
public findNextMatch(searchString:string, rawSearchStart:editorCommon.IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range {
874-
let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
875-
if (!r) {
867+
let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
868+
if (!regex) {
876869
return null;
877870
}
878871

879872
let searchStart = this.validatePosition(rawSearchStart);
880-
if (r.isMultiline) {
881-
return this._doFindNextMatchMultiline(searchStart, r.regex);
873+
if (regex.multiline) {
874+
return this._doFindNextMatchMultiline(searchStart, regex);
882875
}
883-
return this._doFindNextMatchLineByLine(searchStart, r.regex);
876+
return this._doFindNextMatchLineByLine(searchStart, regex);
884877

885878
}
886879

@@ -932,16 +925,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
932925
}
933926

934927
public findPreviousMatch(searchString:string, rawSearchStart:editorCommon.IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range {
935-
let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
936-
if (!r) {
928+
let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
929+
if (!regex) {
937930
return null;
938931
}
939932

940933
let searchStart = this.validatePosition(rawSearchStart);
941-
if (r.isMultiline) {
942-
return this._doFindPreviousMatchMultiline(searchStart, r.regex);
934+
if (regex.multiline) {
935+
return this._doFindPreviousMatchMultiline(searchStart, regex);
943936
}
944-
return this._doFindPreviousMatchLineByLine(searchStart, r.regex);
937+
return this._doFindPreviousMatchLineByLine(searchStart, regex);
945938
}
946939

947940
private _doFindPreviousMatchMultiline(searchStart:Position, searchRegex:RegExp): Range {

src/vs/editor/common/modes/supports/richEditBrackets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ var getReversedRegexForBrackets = once<ISimpleInternalBracket[],RegExp>(
101101

102102
function createOrRegex(pieces:string[]): RegExp {
103103
let regexStr = `(${pieces.map(strings.escapeRegExpCharacters).join(')|(')})`;
104-
return strings.createRegExp(regexStr, true, false, false, false);
104+
return strings.createRegExp(regexStr, true);
105105
}
106106

107107
function toReversedString(str:string): string {

src/vs/editor/test/common/model/model.test.ts

Lines changed: 179 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
IModelContentChangedLinesDeletedEvent, IModelContentChangedLinesInsertedEvent
1414
} from 'vs/editor/common/editorCommon';
1515
import {Model} from 'vs/editor/common/model/model';
16-
import {TextModel, IParsedSearchRequest} from 'vs/editor/common/model/textModel';
16+
import {TextModel} from 'vs/editor/common/model/textModel';
1717

1818
// --------- utils
1919

@@ -599,6 +599,77 @@ suite('Editor Model - Find', () => {
599599
);
600600
});
601601

602+
test('multiline find with line beginning regex', () => {
603+
assertFindMatches(
604+
[
605+
'if',
606+
'else',
607+
'',
608+
'if',
609+
'else'
610+
].join('\n'),
611+
'^if\\nelse', true, false, false,
612+
[
613+
[1, 1, 2, 5],
614+
[4, 1, 5, 5]
615+
]
616+
);
617+
});
618+
619+
test('matching empty lines using boundary expression', () => {
620+
assertFindMatches(
621+
[
622+
'if',
623+
'',
624+
'else',
625+
' ',
626+
'if',
627+
' ',
628+
'else'
629+
].join('\n'),
630+
'^\\s*$\\n', true, false, false,
631+
[
632+
[2, 1, 3, 1],
633+
[4, 1, 5, 1],
634+
[6, 1, 7, 1]
635+
]
636+
);
637+
});
638+
639+
test('matching lines starting with A and ending with B', () => {
640+
assertFindMatches(
641+
[
642+
'a if b',
643+
'a',
644+
'ab',
645+
'eb'
646+
].join('\n'),
647+
'^a.*b$', true, false, false,
648+
[
649+
[1, 1, 1, 7],
650+
[3, 1, 3, 3]
651+
]
652+
);
653+
});
654+
655+
test('multiline find with line ending regex', () => {
656+
assertFindMatches(
657+
[
658+
'if',
659+
'else',
660+
'',
661+
'if',
662+
'elseif',
663+
'else'
664+
].join('\n'),
665+
'if\\nelse$', true, false, false,
666+
[
667+
[1, 1, 2, 5],
668+
[5, 5, 6, 5]
669+
]
670+
);
671+
});
672+
602673
test('issue #4836 - ^.*$', () => {
603674
assertFindMatches(
604675
[
@@ -619,7 +690,97 @@ suite('Editor Model - Find', () => {
619690
);
620691
});
621692

622-
function assertParseSearchResult(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean, expected:IParsedSearchRequest): void {
693+
test('findNextMatch without regex', () => {
694+
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
695+
696+
let actual = testObject.findNextMatch('line', { lineNumber: 1, column: 1 }, false, false, false);
697+
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
698+
699+
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
700+
assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString());
701+
702+
actual = testObject.findNextMatch('line', {lineNumber: 1, column: 3}, false, false, false);
703+
assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString());
704+
705+
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
706+
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
707+
708+
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
709+
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
710+
711+
testObject.dispose();
712+
});
713+
714+
test('findNextMatch with beginning boundary regex', () => {
715+
var testObject = new TextModel([], TextModel.toRawText('line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
716+
717+
let actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 1 }, true, false, false);
718+
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
719+
720+
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
721+
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
722+
723+
actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 3 }, true, false, false);
724+
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
725+
726+
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
727+
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
728+
729+
testObject.dispose();
730+
});
731+
732+
test('findNextMatch with beginning boundary regex and line has repetitive beginnings', () => {
733+
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
734+
735+
let actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 1 }, true, false, false);
736+
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
737+
738+
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
739+
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
740+
741+
actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 3 }, true, false, false);
742+
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
743+
744+
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
745+
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
746+
747+
testObject.dispose();
748+
});
749+
750+
test('findNextMatch with beginning boundary multiline regex and line has repetitive beginnings', () => {
751+
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nline three\nline four', TextModel.DEFAULT_CREATION_OPTIONS));
752+
753+
let actual = testObject.findNextMatch('^line.*\\nline', { lineNumber: 1, column: 1 }, true, false, false);
754+
assert.equal(new Range(1, 1, 2, 5).toString(), actual.toString());
755+
756+
actual = testObject.findNextMatch('^line.*\\nline', actual.getEndPosition(), true, false, false);
757+
assert.equal(new Range(3, 1, 4, 5).toString(), actual.toString());
758+
759+
actual = testObject.findNextMatch('^line.*\\nline', { lineNumber: 2, column: 1 }, true, false, false);
760+
assert.equal(new Range(2, 1, 3, 5).toString(), actual.toString());
761+
762+
testObject.dispose();
763+
});
764+
765+
test('findNextMatch with ending boundary regex', () => {
766+
var testObject = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
767+
768+
let actual = testObject.findNextMatch('line$', { lineNumber: 1, column: 1 }, true, false, false);
769+
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
770+
771+
actual = testObject.findNextMatch('line$', { lineNumber: 1, column: 4 }, true, false, false);
772+
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
773+
774+
actual = testObject.findNextMatch('line$', actual.getEndPosition(), true, false, false);
775+
assert.equal(new Range(2, 5, 2, 9).toString(), actual.toString());
776+
777+
actual = testObject.findNextMatch('line$', actual.getEndPosition(), true, false, false);
778+
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
779+
780+
testObject.dispose();
781+
});
782+
783+
function assertParseSearchResult(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean, expected:RegExp): void {
623784
let actual = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
624785
assert.deepEqual(actual, expected);
625786
}
@@ -631,24 +792,24 @@ suite('Editor Model - Find', () => {
631792
});
632793

633794
test('parseSearchRequest non regex', () => {
634-
assertParseSearchResult('foo', false, false, false, { regex: /foo/gi, isMultiline: false });
635-
assertParseSearchResult('foo', false, false, true, { regex: /\bfoo\b/gi, isMultiline: false });
636-
assertParseSearchResult('foo', false, true, false, { regex: /foo/g, isMultiline: false });
637-
assertParseSearchResult('foo', false, true, true, { regex: /\bfoo\b/g, isMultiline: false });
638-
assertParseSearchResult('foo\\n', false, false, false, { regex: /foo\\n/gi, isMultiline: false });
639-
assertParseSearchResult('foo\\\\n', false, false, false, { regex: /foo\\\\n/gi, isMultiline: false });
640-
assertParseSearchResult('foo\\r', false, false, false, { regex: /foo\\r/gi, isMultiline: false });
641-
assertParseSearchResult('foo\\\\r', false, false, false, { regex: /foo\\\\r/gi, isMultiline: false });
795+
assertParseSearchResult('foo', false, false, false, /foo/gi);
796+
assertParseSearchResult('foo', false, false, true, /\bfoo\b/gi);
797+
assertParseSearchResult('foo', false, true, false, /foo/g);
798+
assertParseSearchResult('foo', false, true, true, /\bfoo\b/g);
799+
assertParseSearchResult('foo\\n', false, false, false, /foo\\n/gi);
800+
assertParseSearchResult('foo\\\\n', false, false, false, /foo\\\\n/gi);
801+
assertParseSearchResult('foo\\r', false, false, false, /foo\\r/gi);
802+
assertParseSearchResult('foo\\\\r', false, false, false, /foo\\\\r/gi);
642803
});
643804

644805
test('parseSearchRequest regex', () => {
645-
assertParseSearchResult('foo', true, false, false, { regex: /foo/gi, isMultiline: false });
646-
assertParseSearchResult('foo', true, false, true, { regex: /\bfoo\b/gi, isMultiline: false });
647-
assertParseSearchResult('foo', true, true, false, { regex: /foo/g, isMultiline: false });
648-
assertParseSearchResult('foo', true, true, true, { regex: /\bfoo\b/g, isMultiline: false });
649-
assertParseSearchResult('foo\\n', true, false, false, { regex: /foo\n/gi, isMultiline: true });
650-
assertParseSearchResult('foo\\\\n', true, false, false, { regex: /foo\\n/gi, isMultiline: false });
651-
assertParseSearchResult('foo\\r', true, false, false, { regex: /foo\r/gi, isMultiline: true });
652-
assertParseSearchResult('foo\\\\r', true, false, false, { regex: /foo\\r/gi, isMultiline: false });
806+
assertParseSearchResult('foo', true, false, false, /foo/gi);
807+
assertParseSearchResult('foo', true, false, true, /\bfoo\b/gi);
808+
assertParseSearchResult('foo', true, true, false, /foo/g);
809+
assertParseSearchResult('foo', true, true, true, /\bfoo\b/g);
810+
assertParseSearchResult('foo\\n', true, false, false, /foo\n/gim);
811+
assertParseSearchResult('foo\\\\n', true, false, false, /foo\\n/gi);
812+
assertParseSearchResult('foo\\r', true, false, false, /foo\r/gim);
813+
assertParseSearchResult('foo\\\\r', true, false, false, /foo\\r/gi);
653814
});
654815
});

0 commit comments

Comments
 (0)