Skip to content

Commit

Permalink
re #45690. Find in chunks.
Browse files Browse the repository at this point in the history
  • Loading branch information
rebornix committed Mar 13, 2018
1 parent 2678fff commit d0daae3
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/vs/editor/common/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { ModelRawContentChangedEvent, IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelLanguageConfigurationChangedEvent, IModelTokensChangedEvent, IModelContentChange } from 'vs/editor/common/model/textModelEvents';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { ITextSnapshot } from 'vs/platform/files/common/files';
import { SearchData } from 'vs/editor/common/model/textModelSearch';

/**
* Vertical Lane in the overview ruler of the editor.
Expand Down Expand Up @@ -1100,6 +1101,7 @@ export interface ITextBuffer {

setEOL(newEOL: '\r\n' | '\n'): void;
applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult;
findMatchesLineByLine?(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[];
}

/**
Expand Down
142 changes: 141 additions & 1 deletion src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { CharCode } from 'vs/base/common/charCode';
import { Range } from 'vs/editor/common/core/range';
import { ITextSnapshot } from 'vs/platform/files/common/files';
import { leftest, righttest, updateTreeMetadata, rbDelete, fixInsert, NodeColor, SENTINEL, TreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase';
import { SearchData, isValidMatch, Searcher, createFindMatch } from 'vs/editor/common/model/textModelSearch';
import { FindMatch } from 'vs/editor/common/model';

// const lfRegex = new RegExp(/\r\n|\r|\n/g);

Expand Down Expand Up @@ -535,6 +537,138 @@ export class PieceTreeBase {
return this.getOffsetAt(lineNumber + 1, 1) - this.getOffsetAt(lineNumber, 1) - this._EOLLength;
}

public findMatchesInNode(node: TreeNode, searcher: Searcher, startLineNumber: number, startCursor: BufferCursor, endCursor: BufferCursor, searchData: SearchData, captureMatches: boolean, limitResultCount: number, resultLen: number, result: FindMatch[]) {
let buffer = this._buffers[node.piece.bufferIndex];
let startOffsetInBuffer = this.offsetInBuffer(node.piece.bufferIndex, node.piece.start);
let start = this.offsetInBuffer(node.piece.bufferIndex, startCursor);
let end = this.offsetInBuffer(node.piece.bufferIndex, endCursor);

let m: RegExpExecArray;
// Reset regex to search from the beginning
searcher.reset(start);
let ret: BufferCursor = { line: 0, column: 0 };

do {
m = searcher.next(buffer.buffer);

if (m) {
if (m.index >= end) {
return resultLen;
}
this.positionInBuffer(node, m.index - startOffsetInBuffer, ret);
let lineFeedCnt = this.getLineFeedCnt(node.piece.bufferIndex, startCursor, ret);
result[resultLen++] = createFindMatch(new Range(startLineNumber + lineFeedCnt, ret.column + 1, startLineNumber + lineFeedCnt, ret.column + 1 + m[0].length), m, captureMatches);

if (m.index + m[0].length >= end) {
return resultLen;
}
if (resultLen >= limitResultCount) {
return resultLen;
}
}

} while (m);

return resultLen;
}

public findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[] {
const result: FindMatch[] = [];
let resultLen = 0;
const searcher = new Searcher(searchData.wordSeparators, searchData.regex);

let startPostion = this.nodeAt2(searchRange.startLineNumber, searchRange.startColumn);
let endPosition = this.nodeAt2(searchRange.endLineNumber, searchRange.endColumn);
let start = this.positionInBuffer(startPostion.node, startPostion.remainder);
let end = this.positionInBuffer(endPosition.node, endPosition.remainder);

if (startPostion.node === endPosition.node) {
this.findMatchesInNode(startPostion.node, searcher, searchRange.startLineNumber, start, end, searchData, captureMatches, limitResultCount, resultLen, result);
return result;
}

let startLineNumber = searchRange.startLineNumber;

let currentNode = startPostion.node;
while (currentNode !== endPosition.node) {
let lineBreakCnt = this.getLineFeedCnt(currentNode.piece.bufferIndex, start, currentNode.piece.end);

if (lineBreakCnt >= 1) {
// last line break position
let lineStarts = this._buffers[currentNode.piece.bufferIndex].lineStarts;
let nextLineStartOffset = lineStarts[start.line + lineBreakCnt];
resultLen = this.findMatchesInNode(currentNode, searcher, startLineNumber, start, this.positionInBuffer(currentNode, nextLineStartOffset), searchData, captureMatches, limitResultCount, resultLen, result);

if (resultLen >= limitResultCount) {
return result;
}

startLineNumber += lineBreakCnt;
}

// search for the remaining content
if (startLineNumber === searchRange.endLineNumber) {
const text = this.getLineContent(startLineNumber).substring(0, searchRange.endColumn - 1);
resultLen = this._findMatchesInLine(searchData, searcher, text, searchRange.endLineNumber, 0, resultLen, result, captureMatches, limitResultCount);
return result;
}

resultLen = this._findMatchesInLine(searchData, searcher, this.getLineContent(startLineNumber), startLineNumber, 0, resultLen, result, captureMatches, limitResultCount);

if (resultLen >= limitResultCount) {
return result;
}

startLineNumber++;
startPostion = this.nodeAt2(startLineNumber, 1);
currentNode = startPostion.node;
start = this.positionInBuffer(startPostion.node, startPostion.remainder);
}

if (startLineNumber === searchRange.endLineNumber) {
const text = this.getLineContent(startLineNumber).substring(0, searchRange.endColumn - 1);
resultLen = this._findMatchesInLine(searchData, searcher, text, searchRange.endLineNumber, 0, resultLen, result, captureMatches, limitResultCount);
return result;
}

resultLen = this.findMatchesInNode(endPosition.node, searcher, startLineNumber, start, end, searchData, captureMatches, limitResultCount, resultLen, result);
return result;
}

private _findMatchesInLine(searchData: SearchData, searcher: Searcher, text: string, lineNumber: number, deltaOffset: number, resultLen: number, result: FindMatch[], captureMatches: boolean, limitResultCount: number): number {
const wordSeparators = searchData.wordSeparators;
if (!captureMatches && searchData.simpleSearch) {
const searchString = searchData.simpleSearch;
const searchStringLen = searchString.length;
const textLength = text.length;

let lastMatchIndex = -searchStringLen;
while ((lastMatchIndex = text.indexOf(searchString, lastMatchIndex + searchStringLen)) !== -1) {
if (!wordSeparators || isValidMatch(wordSeparators, text, textLength, lastMatchIndex, searchStringLen)) {
result[resultLen++] = new FindMatch(new Range(lineNumber, lastMatchIndex + 1 + deltaOffset, lineNumber, lastMatchIndex + 1 + searchStringLen + deltaOffset), null);
if (resultLen >= limitResultCount) {
return resultLen;
}
}
}
return resultLen;
}

let m: RegExpExecArray;
// Reset regex to search from the beginning
searcher.reset(0);
do {
m = searcher.next(text);
if (m) {
result[resultLen++] = createFindMatch(new Range(lineNumber, m.index + 1 + deltaOffset, lineNumber, m.index + 1 + m[0].length + deltaOffset), m, captureMatches);
if (resultLen >= limitResultCount) {
return resultLen;
}
}
} while (m);
return resultLen;
}

// #endregion

// #region Piece Table
Expand Down Expand Up @@ -744,7 +878,7 @@ export class PieceTreeBase {
this.validateCRLFWithPrevNode(newNode);
}

positionInBuffer(node: TreeNode, remainder: number): BufferCursor {
positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor {
let piece = node.piece;
let bufferIndex = node.piece.bufferIndex;
let lineStarts = this._buffers[bufferIndex].lineStarts;
Expand Down Expand Up @@ -780,6 +914,12 @@ export class PieceTreeBase {
}
}

if (ret) {
ret.line = mid;
ret.column = offset - midStart;
return null;
}

return {
line: mid,
column: offset - midStart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { Position } from 'vs/editor/common/core/position';
import * as strings from 'vs/base/common/strings';
import { IValidatedEditOperation } from 'vs/editor/common/model/linesTextBuffer/linesTextBuffer';
import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase';
import { IIdentifiedSingleEditOperation, EndOfLinePreference, ITextBuffer, ApplyEditsResult, IInternalModelContentChange } from 'vs/editor/common/model';
import { IIdentifiedSingleEditOperation, EndOfLinePreference, ITextBuffer, ApplyEditsResult, IInternalModelContentChange, FindMatch } from 'vs/editor/common/model';
import { ITextSnapshot } from 'vs/platform/files/common/files';
import { SearchData } from 'vs/editor/common/model/textModelSearch';

export class PieceTreeTextBuffer implements ITextBuffer {
private _pieceTree: PieceTreeBase;
Expand Down Expand Up @@ -410,6 +411,10 @@ export class PieceTreeTextBuffer implements ITextBuffer {
return contentChanges;
}

findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[] {
return this._pieceTree.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
}

// #endregion

// #region helper
Expand Down
18 changes: 17 additions & 1 deletion src/vs/editor/common/model/textModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { getWordAtText } from 'vs/editor/common/model/wordHelper';
import { ModelLinesTokens, ModelTokensChangedEventBuilder } from 'vs/editor/common/model/textModelTokens';
import { guessIndentation } from 'vs/editor/common/model/indentationGuesser';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch';
import { TextModelSearch, SearchParams, SearchData } from 'vs/editor/common/model/textModelSearch';
import { TPromise } from 'vs/base/common/winjs.base';
import { IStringStream, ITextSnapshot } from 'vs/platform/files/common/files';
import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder';
Expand Down Expand Up @@ -983,6 +983,10 @@ export class TextModel extends Disposable implements model.ITextModel {
return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount));
}

private findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): model.FindMatch[] {
return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
}

public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] {
this._assertNotDisposed();

Expand All @@ -993,6 +997,18 @@ export class TextModel extends Disposable implements model.ITextModel {
searchRange = this.getFullModelRange();
}

if (!isRegex && searchString.indexOf('\n') < 0 && OPTIONS.TEXT_BUFFER_IMPLEMENTATION === TextBufferType.PieceTree) {
// not regex, not multi line
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
const searchData = searchParams.parseSearchRequest();

if (!searchData) {
return [];
}

return this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
}

return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount);
}

Expand Down
16 changes: 13 additions & 3 deletions src/vs/editor/common/model/textModelSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export class SearchData {
}
}

function createFindMatch(range: Range, rawMatches: RegExpExecArray, captureMatches: boolean): FindMatch {
export function createFindMatch(range: Range, rawMatches: RegExpExecArray, captureMatches: boolean): FindMatch {
if (!captureMatches) {
return new FindMatch(range, null);
}
Expand Down Expand Up @@ -434,6 +434,11 @@ function leftIsWordBounday(wordSeparators: WordCharacterClassifier, text: string
return true;
}

if (charBefore === CharCode.CarriageReturn || charBefore === CharCode.LineFeed) {
// The character before the match is line break or carriage return.
return true;
}

if (matchLength > 0) {
const firstCharInMatch = text.charCodeAt(matchStartIndex);
if (wordSeparators.get(firstCharInMatch) !== WordCharacterClass.Regular) {
Expand All @@ -457,6 +462,11 @@ function rightIsWordBounday(wordSeparators: WordCharacterClassifier, text: strin
return true;
}

if (charAfter === CharCode.CarriageReturn || charAfter === CharCode.LineFeed) {
// The character after the match is line break or carriage return.
return true;
}

if (matchLength > 0) {
const lastCharInMatch = text.charCodeAt(matchStartIndex + matchLength - 1);
if (wordSeparators.get(lastCharInMatch) !== WordCharacterClass.Regular) {
Expand All @@ -468,14 +478,14 @@ function rightIsWordBounday(wordSeparators: WordCharacterClassifier, text: strin
return false;
}

function isValidMatch(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
export function isValidMatch(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
return (
leftIsWordBounday(wordSeparators, text, textLength, matchStartIndex, matchLength)
&& rightIsWordBounday(wordSeparators, text, textLength, matchStartIndex, matchLength)
);
}

class Searcher {
export class Searcher {
private _wordSeparators: WordCharacterClassifier;
private _searchRegex: RegExp;
private _prevMatchStartIndex: number;
Expand Down

0 comments on commit d0daae3

Please sign in to comment.