From 1220948f12d27ccc19ff7f5b2386efa5bba421ad Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 11 Jan 2017 10:25:05 +0100 Subject: [PATCH] TextModelSearch can return captured groups --- src/vs/editor/common/editorCommon.ts | 8 +- src/vs/editor/common/model/textModel.ts | 8 +- src/vs/editor/common/model/textModelSearch.ts | 329 ++++++++++-------- .../test/common/model/textModelSearch.test.ts | 192 +++++++--- src/vs/monaco.d.ts | 5 +- 5 files changed, 324 insertions(+), 218 deletions(-) diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 70c3c49d244..d80f06c6b7e 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -1814,13 +1814,15 @@ export interface ITextModel { export class FindMatch { _findMatchBrand: void; - public readonly captures: Range[]; + public readonly range: Range; + public readonly matches: string[]; /** * @internal */ - constructor(captures: Range[]) { - this.captures = captures; + constructor(range: Range, matches: string[]) { + this.range = range; + this.matches = matches; } } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 3d5c91b5d96..17aaaff0633 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -842,17 +842,19 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo searchRange = this.getFullModelRange(); } - return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wholeWord), searchRange, limitResultCount); + return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wholeWord), searchRange, false, limitResultCount).map(e => e.range); } public findNextMatch(searchString: string, rawSearchStart: editorCommon.IPosition, isRegex: boolean, matchCase: boolean, wholeWord: boolean): Range { this._assertNotDisposed(); - return TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wholeWord), rawSearchStart); + let r = TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wholeWord), rawSearchStart, false); + return r ? r.range : null; } public findPreviousMatch(searchString: string, rawSearchStart: editorCommon.IPosition, isRegex: boolean, matchCase: boolean, wholeWord: boolean): Range { this._assertNotDisposed(); - return TextModelSearch.findPreviousMatch(this, new SearchParams(searchString, isRegex, matchCase, wholeWord), rawSearchStart); + let r = TextModelSearch.findPreviousMatch(this, new SearchParams(searchString, isRegex, matchCase, wholeWord), rawSearchStart, false); + return r ? r.range : null; } } diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index 25df65488f1..bc516faa852 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -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 { IPosition } from 'vs/editor/common/editorCommon'; +import { IPosition, FindMatch } from 'vs/editor/common/editorCommon'; import { CharCode } from 'vs/base/common/charCode'; import { TextModel } from 'vs/editor/common/model/textModel'; @@ -87,25 +87,36 @@ export class SearchParams { } } +function createFindMatch(range: Range, rawMatches: RegExpExecArray, captureMatches: boolean): FindMatch { + if (!captureMatches) { + return new FindMatch(range, null); + } + let matches: string[] = []; + for (let i = 0, len = rawMatches.length; i < len; i++) { + matches[i] = rawMatches[i]; + } + return new FindMatch(range, matches); +} + export class TextModelSearch { - public static findMatches(model: TextModel, searchParams: SearchParams, searchRange: Range, limitResultCount: number): Range[] { + public static findMatches(model: TextModel, searchParams: SearchParams, searchRange: Range, captureMatches: boolean, limitResultCount: number): FindMatch[] { const regex = searchParams.parseSearchRequest(); if (!regex) { return []; } if (regex.multiline) { - return this._doFindMatchesMultiline(model, searchRange, regex, limitResultCount); + return this._doFindMatchesMultiline(model, searchRange, regex, captureMatches, limitResultCount); } - return this._doFindMatchesLineByLine(model, searchRange, regex, limitResultCount); + return this._doFindMatchesLineByLine(model, searchRange, regex, captureMatches, limitResultCount); } - private static _doFindMatchesMultiline(model: TextModel, searchRange: Range, searchRegex: RegExp, limitResultCount: number): Range[] { + 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); - const result: Range[] = []; + const result: FindMatch[] = []; let prevStartOffset = 0; let prevEndOffset = 0; let counter = 0; @@ -123,7 +134,11 @@ export class TextModelSearch { const startPosition = model.getPositionAt(startOffset); const endPosition = model.getPositionAt(endOffset); - result[counter++] = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column); + result[counter++] = createFindMatch( + new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column), + m, + captureMatches + ); if (counter >= limitResultCount) { return result; } @@ -135,171 +150,36 @@ export class TextModelSearch { return result; } - private static _doFindMatchesLineByLine(model: TextModel, searchRange: Range, searchRegex: RegExp, limitResultCount: number): Range[] { - const result: Range[] = []; + private static _doFindMatchesLineByLine(model: TextModel, searchRange: Range, searchRegex: RegExp, captureMatches: boolean, limitResultCount: number): FindMatch[] { + const result: FindMatch[] = []; let counter = 0; // Early case for a search range that starts & stops on the same line number if (searchRange.startLineNumber === searchRange.endLineNumber) { const text = model.getLineContent(searchRange.startLineNumber).substring(searchRange.startColumn - 1, searchRange.endColumn - 1); - counter = this._findMatchesInLine(searchRegex, text, searchRange.startLineNumber, searchRange.startColumn - 1, counter, result, limitResultCount); + counter = this._findMatchesInLine(searchRegex, text, searchRange.startLineNumber, searchRange.startColumn - 1, counter, result, captureMatches, limitResultCount); return result; } // Collect results from first line const text = model.getLineContent(searchRange.startLineNumber).substring(searchRange.startColumn - 1); - counter = this._findMatchesInLine(searchRegex, text, searchRange.startLineNumber, searchRange.startColumn - 1, counter, result, limitResultCount); + counter = this._findMatchesInLine(searchRegex, text, searchRange.startLineNumber, searchRange.startColumn - 1, counter, result, captureMatches, limitResultCount); // Collect results from middle lines for (let lineNumber = searchRange.startLineNumber + 1; lineNumber < searchRange.endLineNumber && counter < limitResultCount; lineNumber++) { - counter = this._findMatchesInLine(searchRegex, model.getLineContent(lineNumber), lineNumber, 0, counter, result, limitResultCount); + counter = this._findMatchesInLine(searchRegex, model.getLineContent(lineNumber), lineNumber, 0, counter, result, captureMatches, limitResultCount); } // Collect results from last line if (counter < limitResultCount) { const text = model.getLineContent(searchRange.endLineNumber).substring(0, searchRange.endColumn - 1); - counter = this._findMatchesInLine(searchRegex, text, searchRange.endLineNumber, 0, counter, result, limitResultCount); + counter = this._findMatchesInLine(searchRegex, text, searchRange.endLineNumber, 0, counter, result, captureMatches, limitResultCount); } return result; } - public static findNextMatch(model: TextModel, searchParams: SearchParams, rawSearchStart: IPosition): Range { - const regex = searchParams.parseSearchRequest(); - if (!regex) { - return null; - } - - const searchStart = model.validatePosition(rawSearchStart); - if (regex.multiline) { - return this._doFindNextMatchMultiline(model, searchStart, regex); - } - return this._doFindNextMatchLineByLine(model, searchStart, regex); - - } - - private static _doFindNextMatchMultiline(model: TextModel, searchStart: Position, searchRegex: RegExp): Range { - const searchTextStart: IPosition = { lineNumber: searchStart.lineNumber, column: 1 }; - const deltaOffset = model.getOffsetAt(searchTextStart); - const lineCount = model.getLineCount(); - const text = model.getValueInRange(new Range(searchTextStart.lineNumber, searchTextStart.column, lineCount, model.getLineMaxColumn(lineCount))); - 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 new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column); - } - - if (searchStart.lineNumber !== 1 || searchStart.column !== -1) { - // Try again from the top - return this._doFindNextMatchMultiline(model, new Position(1, 1), searchRegex); - } - - return null; - } - - private static _doFindNextMatchLineByLine(model: TextModel, searchStart: Position, searchRegex: RegExp): Range { - const lineCount = model.getLineCount(); - const startLineNumber = searchStart.lineNumber; - - // Look in first line - const text = model.getLineContent(startLineNumber); - const r = this._findFirstMatchInLine(searchRegex, text, startLineNumber, searchStart.column); - if (r) { - return r; - } - - for (let i = 1; i <= lineCount; i++) { - const lineIndex = (startLineNumber + i - 1) % lineCount; - const text = model.getLineContent(lineIndex + 1); - const r = this._findFirstMatchInLine(searchRegex, text, lineIndex + 1, 1); - if (r) { - return r; - } - } - - return null; - } - - public static findPreviousMatch(model: TextModel, searchParams: SearchParams, rawSearchStart: IPosition): Range { - const regex = searchParams.parseSearchRequest(); - if (!regex) { - return null; - } - - const searchStart = model.validatePosition(rawSearchStart); - if (regex.multiline) { - return this._doFindPreviousMatchMultiline(model, searchStart, regex); - } - return this._doFindPreviousMatchLineByLine(model, searchStart, regex); - } - - private static _doFindPreviousMatchMultiline(model: TextModel, searchStart: Position, searchRegex: RegExp): Range { - const matches = this._doFindMatchesMultiline(model, new Range(1, 1, searchStart.lineNumber, searchStart.column), searchRegex, 10 * LIMIT_FIND_COUNT); - if (matches.length > 0) { - return matches[matches.length - 1]; - } - - const lineCount = model.getLineCount(); - if (searchStart.lineNumber !== lineCount || searchStart.column !== model.getLineMaxColumn(lineCount)) { - // Try again with all content - return this._doFindPreviousMatchMultiline(model, new Position(lineCount, model.getLineMaxColumn(lineCount)), searchRegex); - } - - return null; - } - - private static _doFindPreviousMatchLineByLine(model: TextModel, searchStart: Position, searchRegex: RegExp): Range { - const lineCount = model.getLineCount(); - const startLineNumber = searchStart.lineNumber; - - // Look in first line - const text = model.getLineContent(startLineNumber).substring(0, searchStart.column - 1); - const r = this._findLastMatchInLine(searchRegex, text, startLineNumber); - if (r) { - return r; - } - - for (let i = 1; i <= lineCount; i++) { - const lineIndex = (lineCount + startLineNumber - i - 1) % lineCount; - const text = model.getLineContent(lineIndex + 1); - const r = this._findLastMatchInLine(searchRegex, text, lineIndex + 1); - if (r) { - return r; - } - } - - return null; - } - - private static _findFirstMatchInLine(searchRegex: RegExp, text: string, lineNumber: number, fromColumn: number): Range { - // Set regex to search from column - searchRegex.lastIndex = fromColumn - 1; - const m: RegExpExecArray = searchRegex.exec(text); - return m ? new Range(lineNumber, m.index + 1, lineNumber, m.index + 1 + m[0].length) : null; - } - - private static _findLastMatchInLine(searchRegex: RegExp, text: string, lineNumber: number): Range { - let bestResult: Range = null; - let m: RegExpExecArray; - while ((m = searchRegex.exec(text))) { - const result = new Range(lineNumber, m.index + 1, lineNumber, m.index + 1 + m[0].length); - if (result.equalsRange(bestResult)) { - break; - } - bestResult = result; - if (m.index + m[0].length === text.length) { - // Reached the end of the line - break; - } - } - return bestResult; - } - - private static _findMatchesInLine(searchRegex: RegExp, text: string, lineNumber: number, deltaOffset: number, counter: number, result: Range[], limitResultCount: number): number { + private static _findMatchesInLine(searchRegex: RegExp, text: string, lineNumber: number, deltaOffset: number, counter: number, result: FindMatch[], captureMatches: boolean, limitResultCount: number): number { let m: RegExpExecArray; // Reset regex to search from the beginning searchRegex.lastIndex = 0; @@ -307,11 +187,11 @@ export class TextModelSearch { m = searchRegex.exec(text); if (m) { const range = new Range(lineNumber, m.index + 1 + deltaOffset, lineNumber, m.index + 1 + m[0].length + deltaOffset); - if (range.equalsRange(result[result.length - 1])) { + if (result.length > 0 && range.equalsRange(result[result.length - 1].range)) { // Exit early if the regex matches the same range return counter; } - result.push(range); + result.push(createFindMatch(range, m, captureMatches)); counter++; if (counter >= limitResultCount) { return counter; @@ -324,4 +204,149 @@ export class TextModelSearch { } while (m); return counter; } -} \ No newline at end of file + + public static findNextMatch(model: TextModel, searchParams: SearchParams, rawSearchStart: IPosition, captureMatches: boolean): FindMatch { + const regex = searchParams.parseSearchRequest(); + if (!regex) { + return null; + } + + const searchStart = model.validatePosition(rawSearchStart); + if (regex.multiline) { + return this._doFindNextMatchMultiline(model, searchStart, regex, captureMatches); + } + return this._doFindNextMatchLineByLine(model, searchStart, regex, captureMatches); + } + + private static _doFindNextMatchMultiline(model: TextModel, searchStart: Position, searchRegex: RegExp, captureMatches: boolean): FindMatch { + const searchTextStart: IPosition = { lineNumber: searchStart.lineNumber, column: 1 }; + const deltaOffset = model.getOffsetAt(searchTextStart); + const lineCount = model.getLineCount(); + const text = model.getValueInRange(new Range(searchTextStart.lineNumber, searchTextStart.column, lineCount, model.getLineMaxColumn(lineCount))); + 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), + m, + captureMatches + ); + } + + if (searchStart.lineNumber !== 1 || searchStart.column !== -1) { + // Try again from the top + return this._doFindNextMatchMultiline(model, new Position(1, 1), searchRegex, captureMatches); + } + + return null; + } + + private static _doFindNextMatchLineByLine(model: TextModel, searchStart: Position, searchRegex: RegExp, captureMatches: boolean): FindMatch { + const lineCount = model.getLineCount(); + const startLineNumber = searchStart.lineNumber; + + // Look in first line + const text = model.getLineContent(startLineNumber); + const r = this._findFirstMatchInLine(searchRegex, text, startLineNumber, searchStart.column, captureMatches); + if (r) { + return r; + } + + for (let i = 1; i <= lineCount; i++) { + const lineIndex = (startLineNumber + i - 1) % lineCount; + const text = model.getLineContent(lineIndex + 1); + const r = this._findFirstMatchInLine(searchRegex, text, lineIndex + 1, 1, captureMatches); + if (r) { + return r; + } + } + + return null; + } + + private static _findFirstMatchInLine(searchRegex: RegExp, text: string, lineNumber: number, fromColumn: number, captureMatches: boolean): FindMatch { + // Set regex to search from column + searchRegex.lastIndex = fromColumn - 1; + const m: RegExpExecArray = searchRegex.exec(text); + if (m) { + return createFindMatch( + new Range(lineNumber, m.index + 1, lineNumber, m.index + 1 + m[0].length), + m, + captureMatches + ); + } + return null; + } + + public static findPreviousMatch(model: TextModel, searchParams: SearchParams, rawSearchStart: IPosition, captureMatches: boolean): FindMatch { + const regex = searchParams.parseSearchRequest(); + if (!regex) { + return null; + } + + const searchStart = model.validatePosition(rawSearchStart); + if (regex.multiline) { + return this._doFindPreviousMatchMultiline(model, searchStart, regex, captureMatches); + } + return this._doFindPreviousMatchLineByLine(model, searchStart, regex, captureMatches); + } + + private static _doFindPreviousMatchMultiline(model: TextModel, searchStart: Position, searchRegex: RegExp, captureMatches: boolean): FindMatch { + const matches = this._doFindMatchesMultiline(model, new Range(1, 1, searchStart.lineNumber, searchStart.column), searchRegex, captureMatches, 10 * LIMIT_FIND_COUNT); + if (matches.length > 0) { + return matches[matches.length - 1]; + } + + const lineCount = model.getLineCount(); + if (searchStart.lineNumber !== lineCount || searchStart.column !== model.getLineMaxColumn(lineCount)) { + // Try again with all content + return this._doFindPreviousMatchMultiline(model, new Position(lineCount, model.getLineMaxColumn(lineCount)), searchRegex, captureMatches); + } + + return null; + } + + private static _doFindPreviousMatchLineByLine(model: TextModel, searchStart: Position, searchRegex: RegExp, captureMatches: boolean): FindMatch { + const lineCount = model.getLineCount(); + const startLineNumber = searchStart.lineNumber; + + // Look in first line + const text = model.getLineContent(startLineNumber).substring(0, searchStart.column - 1); + const r = this._findLastMatchInLine(searchRegex, text, startLineNumber, captureMatches); + if (r) { + return r; + } + + for (let i = 1; i <= lineCount; i++) { + const lineIndex = (lineCount + startLineNumber - i - 1) % lineCount; + const text = model.getLineContent(lineIndex + 1); + const r = this._findLastMatchInLine(searchRegex, text, lineIndex + 1, captureMatches); + if (r) { + return r; + } + } + + return null; + } + + private static _findLastMatchInLine(searchRegex: RegExp, text: string, lineNumber: number, captureMatches: boolean): FindMatch { + let bestResult: FindMatch = null; + let m: RegExpExecArray; + while ((m = searchRegex.exec(text))) { + const result = new Range(lineNumber, m.index + 1, lineNumber, m.index + 1 + m[0].length); + if (bestResult && result.equalsRange(bestResult.range)) { + break; + } + bestResult = createFindMatch(result, m, captureMatches); + if (m.index + m[0].length === text.length) { + // Reached the end of the line + break; + } + } + return bestResult; + } +} diff --git a/src/vs/editor/test/common/model/textModelSearch.test.ts b/src/vs/editor/test/common/model/textModelSearch.test.ts index d3aaeeb60ac..0b87c282253 100644 --- a/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { Position } from 'vs/editor/common/core/position'; +import { FindMatch } 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'; @@ -13,36 +14,38 @@ import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelS // --------- Find suite('TextModelSearch', () => { - function toArrRange(r: Range): [number, number, number, number] { - return [r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn]; + function assertFindMatch(actual: FindMatch, expectedRange: Range, expectedMatches: string[] = null): void { + 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 { + 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 actualRanges = model.findMatches(searchString, false, isRegex, matchCase, wholeWord); - let actual = actualRanges.map(toArrRange); + let searchParams = new SearchParams(searchString, isRegex, matchCase, wholeWord); - assert.deepEqual(actual, expected, 'findMatches OK'); + let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), false, 1000); + assert.deepEqual(actual, expectedMatches, 'findMatches OK'); // test `findNextMatch` let startPos = new Position(1, 1); - let match = TextModelSearch.findNextMatch(model, new SearchParams(searchString, isRegex, matchCase, wholeWord), startPos); - assert.deepEqual(toArrRange(match), expected[0], `findNextMatch ${startPos}`); - for (let i = 0; i < expected.length; i++) { - startPos = new Position(expected[i][0], expected[i][1]); - match = TextModelSearch.findNextMatch(model, new SearchParams(searchString, isRegex, matchCase, wholeWord), startPos); - assert.deepEqual(toArrRange(match), expected[i], `findNextMatch ${startPos}`); + let match = TextModelSearch.findNextMatch(model, searchParams, startPos, false); + assert.deepEqual(match, expectedMatches[0], `findNextMatch ${startPos}`); + for (let i = 0; i < expectedMatches.length; i++) { + startPos = expectedMatches[i].range.getStartPosition();; + match = TextModelSearch.findNextMatch(model, searchParams, startPos, false); + assert.deepEqual(match, expectedMatches[i], `findNextMatch ${startPos}`); } // test `findPrevMatch` startPos = new Position(model.getLineCount(), model.getLineMaxColumn(model.getLineCount())); - match = TextModelSearch.findPreviousMatch(model, new SearchParams(searchString, isRegex, matchCase, wholeWord), startPos); - assert.deepEqual(toArrRange(match), expected[expected.length - 1], `findPrevMatch ${startPos}`); - for (let i = 0; i < expected.length; i++) { - startPos = new Position(expected[i][2], expected[i][3]); - match = TextModelSearch.findPreviousMatch(model, new SearchParams(searchString, isRegex, matchCase, wholeWord), startPos); - assert.deepEqual(toArrRange(match), expected[i], `findPrevMatch ${startPos}`); + match = TextModelSearch.findPreviousMatch(model, searchParams, startPos, false); + assert.deepEqual(match, expectedMatches[expectedMatches.length - 1], `findPrevMatch ${startPos}`); + for (let i = 0; i < expectedMatches.length; i++) { + startPos = expectedMatches[i].range.getEndPosition(); + match = TextModelSearch.findPreviousMatch(model, searchParams, startPos, false); + assert.deepEqual(match, expectedMatches[i], `findPrevMatch ${startPos}`); } model.dispose(); @@ -327,20 +330,20 @@ suite('TextModelSearch', () => { let searchParams = new SearchParams('line', false, false, false); - let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }); - assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }, false); + assertFindMatch(actual, new Range(1, 1, 1, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(1, 6, 1, 10)); - actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 3 }); - assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 3 }, false); + assertFindMatch(actual, new Range(1, 6, 1, 10)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(2, 1, 2, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(1, 1, 1, 5)); model.dispose(); }); @@ -350,17 +353,17 @@ suite('TextModelSearch', () => { let searchParams = new SearchParams('^line', true, false, false); - let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }); - assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }, false); + assertFindMatch(actual, new Range(1, 1, 1, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(2, 1, 2, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 3 }); - assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 3 }, false); + assertFindMatch(actual, new Range(2, 1, 2, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(1, 1, 1, 5)); model.dispose(); }); @@ -370,17 +373,17 @@ suite('TextModelSearch', () => { let searchParams = new SearchParams('^line', true, false, false); - let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }); - assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }, false); + assertFindMatch(actual, new Range(1, 1, 1, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(2, 1, 2, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 3 }); - assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 3 }, false); + assertFindMatch(actual, new Range(2, 1, 2, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(1, 1, 1, 5)); model.dispose(); }); @@ -390,14 +393,14 @@ suite('TextModelSearch', () => { let searchParams = new SearchParams('^line.*\\nline', true, false, false); - let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }); - assert.equal(new Range(1, 1, 2, 5).toString(), actual.toString()); + let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }, false); + assertFindMatch(actual, new Range(1, 1, 2, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(3, 1, 4, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(3, 1, 4, 5)); - actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 2, column: 1 }); - assert.equal(new Range(2, 1, 3, 5).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 2, column: 1 }, false); + assertFindMatch(actual, new Range(2, 1, 3, 5)); model.dispose(); }); @@ -407,17 +410,90 @@ suite('TextModelSearch', () => { let searchParams = new SearchParams('line$', true, false, false); - let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }); - assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString()); + let actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 1 }, false); + assertFindMatch(actual, new Range(1, 10, 1, 14)); - actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 4 }); - assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, { lineNumber: 1, column: 4 }, false); + assertFindMatch(actual, new Range(1, 10, 1, 14)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(2, 5, 2, 9).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(2, 5, 2, 9)); - actual = TextModelSearch.findNextMatch(model, searchParams, actual.getEndPosition()); - assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString()); + actual = TextModelSearch.findNextMatch(model, searchParams, actual.range.getEndPosition(), false); + assertFindMatch(actual, new Range(1, 10, 1, 14)); + + model.dispose(); + }); + + test('findMatches with capturing matches', () => { + let model = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let searchParams = new SearchParams('(l(in)e)', true, false, false); + + let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100); + assert.deepEqual(actual, [ + new FindMatch(new Range(1, 5, 1, 9), ['line', 'line', 'in']), + new FindMatch(new Range(1, 10, 1, 14), ['line', 'line', 'in']), + new FindMatch(new Range(2, 5, 2, 9), ['line', 'line', 'in']), + ]); + + model.dispose(); + }); + + test('findMatches multiline with capturing matches', () => { + let model = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let searchParams = new SearchParams('(l(in)e)\\n', true, false, false); + + let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100); + assert.deepEqual(actual, [ + new FindMatch(new Range(1, 10, 2, 1), ['line\n', 'line', 'in']), + new FindMatch(new Range(2, 5, 3, 1), ['line\n', 'line', 'in']), + ]); + + model.dispose(); + }); + + test('findNextMatch with capturing matches', () => { + let model = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let searchParams = new SearchParams('(l(in)e)', true, false, false); + + let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true); + assertFindMatch(actual, new Range(1, 5, 1, 9), ['line', 'line', 'in']); + + model.dispose(); + }); + + test('findNextMatch multiline with capturing matches', () => { + let model = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let searchParams = new SearchParams('(l(in)e)\\n', true, false, false); + + let actual = TextModelSearch.findNextMatch(model, searchParams, new Position(1, 1), true); + assertFindMatch(actual, new Range(1, 10, 2, 1), ['line\n', 'line', 'in']); + + model.dispose(); + }); + + test('findPreviousMatch with capturing matches', () => { + let model = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let searchParams = new SearchParams('(l(in)e)', true, false, false); + + let actual = TextModelSearch.findPreviousMatch(model, searchParams, new Position(1, 1), true); + assertFindMatch(actual, new Range(2, 5, 2, 9), ['line', 'line', 'in']); + + model.dispose(); + }); + + test('findPreviousMatch multiline with capturing matches', () => { + let model = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let searchParams = new SearchParams('(l(in)e)\\n', true, false, false); + + let actual = TextModelSearch.findPreviousMatch(model, searchParams, new Position(1, 1), true); + assertFindMatch(actual, new Range(2, 5, 3, 1), ['line\n', 'line', 'in']); model.dispose(); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ac36ef92743..f4f46ba3155 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2090,8 +2090,9 @@ declare module monaco.editor { } export class FindMatch { - readonly captures: Range[]; - constructor(captures: Range[]); + _findMatchBrand: void; + readonly range: Range; + readonly matches: string[]; } export interface IReadOnlyModel extends ITextModel {