This commit is contained in:
Sandeep Somavarapu 2016-09-06 14:46:09 +02:00
parent 97ddcf6220
commit c6b3f39c63
8 changed files with 219 additions and 133 deletions

4
.vscode/launch.json vendored
View file

@ -9,7 +9,9 @@
"stopOnEntry": false,
"args": [
"--timeout",
"999999"
"999999",
"-g",
"Editor Model - Find"
],
"cwd": "${workspaceRoot}",
"runtimeArgs": [],

View file

@ -174,14 +174,21 @@ export function endsWith(haystack: string, needle: string): boolean {
}
}
export function createRegExp(searchString: string, isRegex: boolean, matchCase: boolean, wholeWord: boolean, global:boolean): RegExp {
export interface RegExpOptions {
matchCase?: boolean;
wholeWord?: boolean;
multiline?: boolean;
global?: boolean;
}
export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp {
if (searchString === '') {
throw new Error('Cannot create regex from empty string');
}
if (!isRegex) {
searchString = searchString.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&');
}
if (wholeWord) {
if (options.wholeWord) {
if (!/\B/.test(searchString.charAt(0))) {
searchString = '\\b' + searchString;
}
@ -190,12 +197,15 @@ export function createRegExp(searchString: string, isRegex: boolean, matchCase:
}
}
let modifiers = '';
if (global) {
if (options.global) {
modifiers += 'g';
}
if (!matchCase) {
if (!options.matchCase) {
modifiers += 'i';
}
if (options.multiline) {
modifiers += 'm';
}
return new RegExp(searchString, modifiers);
}

View file

@ -18,11 +18,6 @@ import {IndentRange, computeRanges} from 'vs/editor/common/model/indentRanges';
const LIMIT_FIND_COUNT = 999;
export const LONG_LINE_BOUNDARY = 1000;
export interface IParsedSearchRequest {
regex: RegExp;
isMultiline: boolean;
}
export class TextModel extends OrderGuaranteeEventEmitter implements editorCommon.ITextModel {
private static MODEL_SYNC_LIMIT = 5 * 1024 * 1024; // 5 MB
private static MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB
@ -764,15 +759,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
return false;
}
public static parseSearchRequest(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean): IParsedSearchRequest {
public static parseSearchRequest(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean): RegExp {
if (searchString === '') {
return null;
}
// Try to create a RegExp out of the params
var regex:RegExp = null;
var regex: RegExp = null;
var multiline = isRegex && TextModel._isMultiline(searchString);
try {
regex = strings.createRegExp(searchString, isRegex, matchCase, wholeWord, true);
regex = strings.createRegExp(searchString, isRegex, {matchCase, wholeWord, multiline, global: true});
} catch (err) {
return null;
}
@ -781,15 +777,12 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
return null;
}
return {
regex: regex,
isMultiline: isRegex && TextModel._isMultiline(searchString)
};
return regex;
}
public findMatches(searchString:string, rawSearchScope:any, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount:number = LIMIT_FIND_COUNT): Range[] {
let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
if (!r) {
let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
if (!regex) {
return [];
}
@ -800,10 +793,10 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
searchRange = this.getFullModelRange();
}
if (r.isMultiline) {
return this._doFindMatchesMultiline(searchRange, r.regex, limitResultCount);
if (regex.multiline) {
return this._doFindMatchesMultiline(searchRange, regex, limitResultCount);
}
return this._doFindMatchesLineByLine(searchRange, r.regex, limitResultCount);
return this._doFindMatchesLineByLine(searchRange, regex, limitResultCount);
}
private _doFindMatchesMultiline(searchRange:Range, searchRegex:RegExp, limitResultCount:number): Range[] {
@ -871,16 +864,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
}
public findNextMatch(searchString:string, rawSearchStart:editorCommon.IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range {
let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
if (!r) {
let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
if (!regex) {
return null;
}
let searchStart = this.validatePosition(rawSearchStart);
if (r.isMultiline) {
return this._doFindNextMatchMultiline(searchStart, r.regex);
if (regex.multiline) {
return this._doFindNextMatchMultiline(searchStart, regex);
}
return this._doFindNextMatchLineByLine(searchStart, r.regex);
return this._doFindNextMatchLineByLine(searchStart, regex);
}
@ -932,16 +925,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
}
public findPreviousMatch(searchString:string, rawSearchStart:editorCommon.IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range {
let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
if (!r) {
let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
if (!regex) {
return null;
}
let searchStart = this.validatePosition(rawSearchStart);
if (r.isMultiline) {
return this._doFindPreviousMatchMultiline(searchStart, r.regex);
if (regex.multiline) {
return this._doFindPreviousMatchMultiline(searchStart, regex);
}
return this._doFindPreviousMatchLineByLine(searchStart, r.regex);
return this._doFindPreviousMatchLineByLine(searchStart, regex);
}
private _doFindPreviousMatchMultiline(searchStart:Position, searchRegex:RegExp): Range {

View file

@ -101,7 +101,7 @@ var getReversedRegexForBrackets = once<ISimpleInternalBracket[],RegExp>(
function createOrRegex(pieces:string[]): RegExp {
let regexStr = `(${pieces.map(strings.escapeRegExpCharacters).join(')|(')})`;
return strings.createRegExp(regexStr, true, false, false, false);
return strings.createRegExp(regexStr, true);
}
function toReversedString(str:string): string {

View file

@ -13,7 +13,7 @@ import {
IModelContentChangedLinesDeletedEvent, IModelContentChangedLinesInsertedEvent
} from 'vs/editor/common/editorCommon';
import {Model} from 'vs/editor/common/model/model';
import {TextModel, IParsedSearchRequest} from 'vs/editor/common/model/textModel';
import {TextModel} from 'vs/editor/common/model/textModel';
// --------- utils
@ -599,6 +599,77 @@ suite('Editor Model - Find', () => {
);
});
test('multiline find with line beginning regex', () => {
assertFindMatches(
[
'if',
'else',
'',
'if',
'else'
].join('\n'),
'^if\\nelse', true, false, false,
[
[1, 1, 2, 5],
[4, 1, 5, 5]
]
);
});
test('matching empty lines using boundary expression', () => {
assertFindMatches(
[
'if',
'',
'else',
' ',
'if',
' ',
'else'
].join('\n'),
'^\\s*$\\n', true, false, false,
[
[2, 1, 3, 1],
[4, 1, 5, 1],
[6, 1, 7, 1]
]
);
});
test('matching lines starting with A and ending with B', () => {
assertFindMatches(
[
'a if b',
'a',
'ab',
'eb'
].join('\n'),
'^a.*b$', true, false, false,
[
[1, 1, 1, 7],
[3, 1, 3, 3]
]
);
});
test('multiline find with line ending regex', () => {
assertFindMatches(
[
'if',
'else',
'',
'if',
'elseif',
'else'
].join('\n'),
'if\\nelse$', true, false, false,
[
[1, 1, 2, 5],
[5, 5, 6, 5]
]
);
});
test('issue #4836 - ^.*$', () => {
assertFindMatches(
[
@ -619,7 +690,97 @@ suite('Editor Model - Find', () => {
);
});
function assertParseSearchResult(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean, expected:IParsedSearchRequest): void {
test('findNextMatch without regex', () => {
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('line', { lineNumber: 1, column: 1 }, false, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString());
actual = testObject.findNextMatch('line', {lineNumber: 1, column: 3}, false, false, false);
assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString());
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
testObject.dispose();
});
test('findNextMatch with beginning boundary regex', () => {
var testObject = new TextModel([], TextModel.toRawText('line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 1 }, true, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 3 }, true, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
testObject.dispose();
});
test('findNextMatch with beginning boundary regex and line has repetitive beginnings', () => {
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 1 }, true, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 3 }, true, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
testObject.dispose();
});
test('findNextMatch with beginning boundary multiline regex and line has repetitive beginnings', () => {
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nline three\nline four', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('^line.*\\nline', { lineNumber: 1, column: 1 }, true, false, false);
assert.equal(new Range(1, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line.*\\nline', actual.getEndPosition(), true, false, false);
assert.equal(new Range(3, 1, 4, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line.*\\nline', { lineNumber: 2, column: 1 }, true, false, false);
assert.equal(new Range(2, 1, 3, 5).toString(), actual.toString());
testObject.dispose();
});
test('findNextMatch with ending boundary regex', () => {
var testObject = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('line$', { lineNumber: 1, column: 1 }, true, false, false);
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
actual = testObject.findNextMatch('line$', { lineNumber: 1, column: 4 }, true, false, false);
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
actual = testObject.findNextMatch('line$', actual.getEndPosition(), true, false, false);
assert.equal(new Range(2, 5, 2, 9).toString(), actual.toString());
actual = testObject.findNextMatch('line$', actual.getEndPosition(), true, false, false);
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
testObject.dispose();
});
function assertParseSearchResult(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean, expected:RegExp): void {
let actual = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord);
assert.deepEqual(actual, expected);
}
@ -631,24 +792,24 @@ suite('Editor Model - Find', () => {
});
test('parseSearchRequest non regex', () => {
assertParseSearchResult('foo', false, false, false, { regex: /foo/gi, isMultiline: false });
assertParseSearchResult('foo', false, false, true, { regex: /\bfoo\b/gi, isMultiline: false });
assertParseSearchResult('foo', false, true, false, { regex: /foo/g, isMultiline: false });
assertParseSearchResult('foo', false, true, true, { regex: /\bfoo\b/g, isMultiline: false });
assertParseSearchResult('foo\\n', false, false, false, { regex: /foo\\n/gi, isMultiline: false });
assertParseSearchResult('foo\\\\n', false, false, false, { regex: /foo\\\\n/gi, isMultiline: false });
assertParseSearchResult('foo\\r', false, false, false, { regex: /foo\\r/gi, isMultiline: false });
assertParseSearchResult('foo\\\\r', false, false, false, { regex: /foo\\\\r/gi, isMultiline: false });
assertParseSearchResult('foo', false, false, false, /foo/gi);
assertParseSearchResult('foo', false, false, true, /\bfoo\b/gi);
assertParseSearchResult('foo', false, true, false, /foo/g);
assertParseSearchResult('foo', false, true, true, /\bfoo\b/g);
assertParseSearchResult('foo\\n', false, false, false, /foo\\n/gi);
assertParseSearchResult('foo\\\\n', false, false, false, /foo\\\\n/gi);
assertParseSearchResult('foo\\r', false, false, false, /foo\\r/gi);
assertParseSearchResult('foo\\\\r', false, false, false, /foo\\\\r/gi);
});
test('parseSearchRequest regex', () => {
assertParseSearchResult('foo', true, false, false, { regex: /foo/gi, isMultiline: false });
assertParseSearchResult('foo', true, false, true, { regex: /\bfoo\b/gi, isMultiline: false });
assertParseSearchResult('foo', true, true, false, { regex: /foo/g, isMultiline: false });
assertParseSearchResult('foo', true, true, true, { regex: /\bfoo\b/g, isMultiline: false });
assertParseSearchResult('foo\\n', true, false, false, { regex: /foo\n/gi, isMultiline: true });
assertParseSearchResult('foo\\\\n', true, false, false, { regex: /foo\\n/gi, isMultiline: false });
assertParseSearchResult('foo\\r', true, false, false, { regex: /foo\r/gi, isMultiline: true });
assertParseSearchResult('foo\\\\r', true, false, false, { regex: /foo\\r/gi, isMultiline: false });
assertParseSearchResult('foo', true, false, false, /foo/gi);
assertParseSearchResult('foo', true, false, true, /\bfoo\b/gi);
assertParseSearchResult('foo', true, true, false, /foo/g);
assertParseSearchResult('foo', true, true, true, /\bfoo\b/g);
assertParseSearchResult('foo\\n', true, false, false, /foo\n/gim);
assertParseSearchResult('foo\\\\n', true, false, false, /foo\\n/gi);
assertParseSearchResult('foo\\r', true, false, false, /foo\r/gim);
assertParseSearchResult('foo\\\\r', true, false, false, /foo\\r/gi);
});
});

View file

@ -555,86 +555,6 @@ suite('Editor Model - TextModel', () => {
model.dispose();
});
test('findNextMatch without regex', () => {
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('line', { lineNumber: 1, column: 1 }, false, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString());
actual = testObject.findNextMatch('line', {lineNumber: 1, column: 3}, false, false, false);
assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString());
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
});
test('findNextMatch with beginning boundary regex', () => {
var testObject = new TextModel([], TextModel.toRawText('line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 1 }, true, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 3 }, true, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
});
test('findNextMatch with beginning boundary regex and line has repetitive beginnings', () => {
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 1 }, true, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 3 }, true, false, false);
assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false);
assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString());
});
test('findNextMatch with beginning boundary multiline regex and line has repetitive beginnings', () => {
var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nline three', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('^line.*\\nline', { lineNumber: 1, column: 1 }, true, false, false);
assert.equal(new Range(1, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line.*\\nline', actual.getEndPosition(), true, false, false);
assert.equal(new Range(1, 1, 2, 5).toString(), actual.toString());
actual = testObject.findNextMatch('^line.*\\nline', { lineNumber: 2, column: 1 }, true, false, false);
assert.equal(new Range(2, 1, 3, 5).toString(), actual.toString());
});
test('findNextMatch with ending boundary regex', () => {
var testObject = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS));
let actual = testObject.findNextMatch('line$', { lineNumber: 1, column: 1 }, true, false, false);
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
actual = testObject.findNextMatch('line$', { lineNumber: 1, column: 4 }, true, false, false);
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
actual = testObject.findNextMatch('line$', actual.getEndPosition(), true, false, false);
assert.equal(new Range(2, 5, 2, 9).toString(), actual.toString());
actual = testObject.findNextMatch('line$', actual.getEndPosition(), true, false, false);
assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString());
});
});
suite('TextModel.getLineIndentGuide', () => {

View file

@ -26,7 +26,7 @@ export class ReplacePattern {
constructor(private replaceString: string, private searchPatternInfo: IPatternInfo) {
this._replacePattern= replaceString;
if (searchPatternInfo.isRegExp) {
this._searchRegExp= strings.createRegExp(searchPatternInfo.pattern, searchPatternInfo.isRegExp, searchPatternInfo.isCaseSensitive, searchPatternInfo.isWordMatch, true);
this._searchRegExp= strings.createRegExp(searchPatternInfo.pattern, searchPatternInfo.isRegExp, {matchCase: searchPatternInfo.isCaseSensitive, wholeWord: searchPatternInfo.isWordMatch, multiline: false, global: true});
this.parseReplaceString(replaceString);
}
}

View file

@ -45,7 +45,7 @@ export class Engine implements ISearchEngine<ISerializedFileMatch> {
this.rootFolders = config.rootFolders;
this.extraFiles = config.extraFiles;
this.walker = walker;
this.contentPattern = strings.createRegExp(config.contentPattern.pattern, config.contentPattern.isRegExp, config.contentPattern.isCaseSensitive, config.contentPattern.isWordMatch, true);
this.contentPattern = strings.createRegExp(config.contentPattern.pattern, config.contentPattern.isRegExp, {matchCase: config.contentPattern.isCaseSensitive, wholeWord: config.contentPattern.isWordMatch, multiline: false, global: true});
this.isCanceled = false;
this.limitReached = false;
this.maxResults = config.maxResults;