mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-10 05:28:19 -05:00
Fixes #19225: Have \n match EOL sequence regardless of model's EOL sequence
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user