提交 4e196ffb 编写于 作者: A Alex Dima

Fixes #19225: Have \n match EOL sequence regardless of model's EOL sequence

上级 5c31cac0
......@@ -7,7 +7,7 @@
import * as strings from 'vs/base/common/strings';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { FindMatch } from 'vs/editor/common/editorCommon';
import { FindMatch, EndOfLinePreference } from 'vs/editor/common/editorCommon';
import { CharCode } from 'vs/base/common/charCode';
import { TextModel } from 'vs/editor/common/model/textModel';
......@@ -112,9 +112,50 @@ export class TextModelSearch {
return this._doFindMatchesLineByLine(model, searchRange, regex, captureMatches, limitResultCount);
}
/**
* Multiline search always executes on the lines concatenated with \n.
* We must therefore compensate for the count of \n in case the model is CRLF
*/
private static _getMultilineMatchRange(model: TextModel, deltaOffset: number, text: string, matchIndex: number, match0: string): Range {
let startOffset: number;
if (model.getEOL() === '\r\n') {
let lineFeedCountBeforeMatch = 0;
for (let i = 0; i < matchIndex; i++) {
let chCode = text.charCodeAt(i);
if (chCode === CharCode.LineFeed) {
lineFeedCountBeforeMatch++;
}
}
startOffset = deltaOffset + matchIndex + lineFeedCountBeforeMatch /* add as many \r as there were \n */;
} else {
startOffset = deltaOffset + matchIndex;
}
let endOffset: number;
if (model.getEOL() === '\r\n') {
let lineFeedCountInMatch = 0;
for (let i = 0, len = match0.length; i < len; i++) {
let chCode = text.charCodeAt(i + matchIndex);
if (chCode === CharCode.LineFeed) {
lineFeedCountInMatch++;
}
}
endOffset = startOffset + match0.length + lineFeedCountInMatch /* add as many \r as there were \n */;
} else {
endOffset = startOffset + match0.length;
}
const startPosition = model.getPositionAt(startOffset);
const endPosition = model.getPositionAt(endOffset);
return new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);
}
private static _doFindMatchesMultiline(model: TextModel, searchRange: Range, searchRegex: RegExp, captureMatches: boolean, limitResultCount: number): FindMatch[] {
const deltaOffset = model.getOffsetAt(searchRange.getStartPosition());
const text = model.getValueInRange(searchRange);
// We always execute multiline search over the lines joined with \n
// This makes it that \n will match the EOL for both CRLF and LF models
// We compensate for offset errors in `_getMultilineMatchRange`
const text = model.getValueInRange(searchRange, EndOfLinePreference.LF);
const result: FindMatch[] = [];
let prevStartOffset = 0;
......@@ -131,11 +172,8 @@ export class TextModelSearch {
return result;
}
const startPosition = model.getPositionAt(startOffset);
const endPosition = model.getPositionAt(endOffset);
result[counter++] = createFindMatch(
new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column),
this._getMultilineMatchRange(model, deltaOffset, text, m.index, m[0]),
m,
captureMatches
);
......@@ -221,22 +259,21 @@ export class TextModelSearch {
const searchTextStart = new Position(searchStart.lineNumber, 1);
const deltaOffset = model.getOffsetAt(searchTextStart);
const lineCount = model.getLineCount();
const text = model.getValueInRange(new Range(searchTextStart.lineNumber, searchTextStart.column, lineCount, model.getLineMaxColumn(lineCount)));
// We always execute multiline search over the lines joined with \n
// This makes it that \n will match the EOL for both CRLF and LF models
// We compensate for offset errors in `_getMultilineMatchRange`
const text = model.getValueInRange(new Range(searchTextStart.lineNumber, searchTextStart.column, lineCount, model.getLineMaxColumn(lineCount)), EndOfLinePreference.LF);
searchRegex.lastIndex = searchStart.column - 1;
let m = searchRegex.exec(text);
if (m) {
const startOffset = deltaOffset + m.index;
const endOffset = startOffset + m[0].length;
const startPosition = model.getPositionAt(startOffset);
const endPosition = model.getPositionAt(endOffset);
return createFindMatch(
new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column),
this._getMultilineMatchRange(model, deltaOffset, text, m.index, m[0]),
m,
captureMatches
);
}
if (searchStart.lineNumber !== 1 || searchStart.column !== -1) {
if (searchStart.lineNumber !== 1 || searchStart.column !== 1) {
// Try again from the top
return this._doFindNextMatchMultiline(model, new Position(1, 1), searchRegex, captureMatches);
}
......
......@@ -6,7 +6,7 @@
import * as assert from 'assert';
import { Position } from 'vs/editor/common/core/position';
import { FindMatch } from 'vs/editor/common/editorCommon';
import { FindMatch, EndOfLineSequence } from 'vs/editor/common/editorCommon';
import { Range } from 'vs/editor/common/core/range';
import { TextModel } from 'vs/editor/common/model/textModel';
import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch';
......@@ -18,13 +18,7 @@ suite('TextModelSearch', () => {
assert.deepEqual(actual, new FindMatch(expectedRange, expectedMatches));
}
function assertFindMatches(text: string, searchString: string, isRegex: boolean, matchCase: boolean, wholeWord: boolean, _expected: [number, number, number, number][]): void {
let expectedRanges = _expected.map(entry => new Range(entry[0], entry[1], entry[2], entry[3]));
let expectedMatches = expectedRanges.map(entry => new FindMatch(entry, null));
let model = new TextModel([], TextModel.toRawText(text, TextModel.DEFAULT_CREATION_OPTIONS));
let searchParams = new SearchParams(searchString, isRegex, matchCase, wholeWord);
function _assertFindMatches(model: TextModel, searchParams: SearchParams, expectedMatches: FindMatch[]): void {
let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), false, 1000);
assert.deepEqual(actual, expectedMatches, 'findMatches OK');
......@@ -47,8 +41,22 @@ suite('TextModelSearch', () => {
match = TextModelSearch.findPreviousMatch(model, searchParams, startPos, false);
assert.deepEqual(match, expectedMatches[i], `findPrevMatch ${startPos}`);
}
}
function assertFindMatches(text: string, searchString: string, isRegex: boolean, matchCase: boolean, wholeWord: boolean, _expected: [number, number, number, number][]): void {
let expectedRanges = _expected.map(entry => new Range(entry[0], entry[1], entry[2], entry[3]));
let expectedMatches = expectedRanges.map(entry => new FindMatch(entry, null));
let searchParams = new SearchParams(searchString, isRegex, matchCase, wholeWord);
let model = new TextModel([], TextModel.toRawText(text, TextModel.DEFAULT_CREATION_OPTIONS));
_assertFindMatches(model, searchParams, expectedMatches);
model.dispose();
let model2 = new TextModel([], TextModel.toRawText(text, TextModel.DEFAULT_CREATION_OPTIONS));
model2.setEOL(EndOfLineSequence.CRLF);
_assertFindMatches(model2, searchParams, expectedMatches);
model2.dispose();
}
let regularText = [
......@@ -498,6 +506,42 @@ suite('TextModelSearch', () => {
model.dispose();
});
test('\\n matches \\r\\n', () => {
let model = new TextModel([], TextModel.toRawText('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni', TextModel.DEFAULT_CREATION_OPTIONS));
assert.equal(model.getEOL(), '\r\n');
let searchParams = new SearchParams('h\\n', true, false, false);
let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000)[0];
assertFindMatch(actual, new Range(8, 1, 9, 1), ['h\n']);
searchParams = new SearchParams('g\\nh\\n', true, false, false);
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000)[0];
assertFindMatch(actual, new Range(7, 1, 9, 1), ['g\nh\n']);
searchParams = new SearchParams('\\ni', true, false, false);
actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000)[0];
assertFindMatch(actual, new Range(8, 2, 9, 2), ['\ni']);
model.dispose();
});
test('\\r can never be found', () => {
let model = new TextModel([], TextModel.toRawText('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni', TextModel.DEFAULT_CREATION_OPTIONS));
assert.equal(model.getEOL(), '\r\n');
let searchParams = new SearchParams('\\r\\n', true, false, false);
let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true);
assert.equal(actual, null);
assert.deepEqual(TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 1000), []);
model.dispose();
});
function assertParseSearchResult(searchString: string, isRegex: boolean, matchCase: boolean, wholeWord: boolean, expected: RegExp): void {
let searchParams = new SearchParams(searchString, isRegex, matchCase, wholeWord);
let actual = searchParams.parseSearchRequest();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册