UNPKG

monaco-editor

Version:
1,033 lines • 61.6 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Position } from '../../core/position.js'; import { Range } from '../../core/range.js'; import { FindMatch } from '../../model.js'; import { SENTINEL, TreeNode, fixInsert, leftest, rbDelete, righttest, updateTreeMetadata } from './rbTreeBase.js'; import { Searcher, createFindMatch, isValidMatch } from '../textModelSearch.js'; // const lfRegex = new RegExp(/\r\n|\r|\n/g); export var AverageBufferSize = 65535; export function createUintArray(arr) { var r; if (arr[arr.length - 1] < 65536) { r = new Uint16Array(arr.length); } else { r = new Uint32Array(arr.length); } r.set(arr, 0); return r; } var LineStarts = /** @class */ (function () { function LineStarts(lineStarts, cr, lf, crlf, isBasicASCII) { this.lineStarts = lineStarts; this.cr = cr; this.lf = lf; this.crlf = crlf; this.isBasicASCII = isBasicASCII; } return LineStarts; }()); export { LineStarts }; export function createLineStartsFast(str, readonly) { if (readonly === void 0) { readonly = true; } var r = [0], rLength = 1; for (var i = 0, len = str.length; i < len; i++) { var chr = str.charCodeAt(i); if (chr === 13 /* CarriageReturn */) { if (i + 1 < len && str.charCodeAt(i + 1) === 10 /* LineFeed */) { // \r\n... case r[rLength++] = i + 2; i++; // skip \n } else { // \r... case r[rLength++] = i + 1; } } else if (chr === 10 /* LineFeed */) { r[rLength++] = i + 1; } } if (readonly) { return createUintArray(r); } else { return r; } } export function createLineStarts(r, str) { r.length = 0; r[0] = 0; var rLength = 1; var cr = 0, lf = 0, crlf = 0; var isBasicASCII = true; for (var i = 0, len = str.length; i < len; i++) { var chr = str.charCodeAt(i); if (chr === 13 /* CarriageReturn */) { if (i + 1 < len && str.charCodeAt(i + 1) === 10 /* LineFeed */) { // \r\n... case crlf++; r[rLength++] = i + 2; i++; // skip \n } else { cr++; // \r... case r[rLength++] = i + 1; } } else if (chr === 10 /* LineFeed */) { lf++; r[rLength++] = i + 1; } else { if (isBasicASCII) { if (chr !== 9 /* Tab */ && (chr < 32 || chr > 126)) { isBasicASCII = false; } } } } var result = new LineStarts(createUintArray(r), cr, lf, crlf, isBasicASCII); r.length = 0; return result; } var Piece = /** @class */ (function () { function Piece(bufferIndex, start, end, lineFeedCnt, length) { this.bufferIndex = bufferIndex; this.start = start; this.end = end; this.lineFeedCnt = lineFeedCnt; this.length = length; } return Piece; }()); export { Piece }; var StringBuffer = /** @class */ (function () { function StringBuffer(buffer, lineStarts) { this.buffer = buffer; this.lineStarts = lineStarts; } return StringBuffer; }()); export { StringBuffer }; var PieceTreeSearchCache = /** @class */ (function () { function PieceTreeSearchCache(limit) { this._limit = limit; this._cache = []; } PieceTreeSearchCache.prototype.get = function (offset) { for (var i = this._cache.length - 1; i >= 0; i--) { var nodePos = this._cache[i]; if (nodePos.nodeStartOffset <= offset && nodePos.nodeStartOffset + nodePos.node.piece.length >= offset) { return nodePos; } } return null; }; PieceTreeSearchCache.prototype.get2 = function (lineNumber) { for (var i = this._cache.length - 1; i >= 0; i--) { var nodePos = this._cache[i]; if (nodePos.nodeStartLineNumber && nodePos.nodeStartLineNumber < lineNumber && nodePos.nodeStartLineNumber + nodePos.node.piece.lineFeedCnt >= lineNumber) { return nodePos; } } return null; }; PieceTreeSearchCache.prototype.set = function (nodePosition) { if (this._cache.length >= this._limit) { this._cache.shift(); } this._cache.push(nodePosition); }; PieceTreeSearchCache.prototype.valdiate = function (offset) { var hasInvalidVal = false; var tmp = this._cache; for (var i = 0; i < tmp.length; i++) { var nodePos = tmp[i]; if (nodePos.node.parent === null || nodePos.nodeStartOffset >= offset) { tmp[i] = null; hasInvalidVal = true; continue; } } if (hasInvalidVal) { var newArr = []; for (var i = 0; i < tmp.length; i++) { var entry = tmp[i]; if (entry !== null) { newArr.push(entry); } } this._cache = newArr; } }; return PieceTreeSearchCache; }()); var PieceTreeBase = /** @class */ (function () { function PieceTreeBase(chunks, eol, eolNormalized) { this.create(chunks, eol, eolNormalized); } PieceTreeBase.prototype.create = function (chunks, eol, eolNormalized) { this._buffers = [ new StringBuffer('', [0]) ]; this._lastChangeBufferPos = { line: 0, column: 0 }; this.root = SENTINEL; this._lineCnt = 1; this._length = 0; this._EOL = eol; this._EOLLength = eol.length; this._EOLNormalized = eolNormalized; var lastNode = null; for (var i = 0, len = chunks.length; i < len; i++) { if (chunks[i].buffer.length > 0) { if (!chunks[i].lineStarts) { chunks[i].lineStarts = createLineStartsFast(chunks[i].buffer); } var piece = new Piece(i + 1, { line: 0, column: 0 }, { line: chunks[i].lineStarts.length - 1, column: chunks[i].buffer.length - chunks[i].lineStarts[chunks[i].lineStarts.length - 1] }, chunks[i].lineStarts.length - 1, chunks[i].buffer.length); this._buffers.push(chunks[i]); lastNode = this.rbInsertRight(lastNode, piece); } } this._searchCache = new PieceTreeSearchCache(1); this._lastVisitedLine = { lineNumber: 0, value: '' }; this.computeBufferMetadata(); }; PieceTreeBase.prototype.normalizeEOL = function (eol) { var _this = this; var averageBufferSize = AverageBufferSize; var min = averageBufferSize - Math.floor(averageBufferSize / 3); var max = min * 2; var tempChunk = ''; var tempChunkLen = 0; var chunks = []; this.iterate(this.root, function (node) { var str = _this.getNodeContent(node); var len = str.length; if (tempChunkLen <= min || tempChunkLen + len < max) { tempChunk += str; tempChunkLen += len; return true; } // flush anyways var text = tempChunk.replace(/\r\n|\r|\n/g, eol); chunks.push(new StringBuffer(text, createLineStartsFast(text))); tempChunk = str; tempChunkLen = len; return true; }); if (tempChunkLen > 0) { var text = tempChunk.replace(/\r\n|\r|\n/g, eol); chunks.push(new StringBuffer(text, createLineStartsFast(text))); } this.create(chunks, eol, true); }; // #region Buffer API PieceTreeBase.prototype.getEOL = function () { return this._EOL; }; PieceTreeBase.prototype.setEOL = function (newEOL) { this._EOL = newEOL; this._EOLLength = this._EOL.length; this.normalizeEOL(newEOL); }; PieceTreeBase.prototype.getOffsetAt = function (lineNumber, column) { var leftLen = 0; // inorder var x = this.root; while (x !== SENTINEL) { if (x.left !== SENTINEL && x.lf_left + 1 >= lineNumber) { x = x.left; } else if (x.lf_left + x.piece.lineFeedCnt + 1 >= lineNumber) { leftLen += x.size_left; // lineNumber >= 2 var accumualtedValInCurrentIndex = this.getAccumulatedValue(x, lineNumber - x.lf_left - 2); return leftLen += accumualtedValInCurrentIndex + column - 1; } else { lineNumber -= x.lf_left + x.piece.lineFeedCnt; leftLen += x.size_left + x.piece.length; x = x.right; } } return leftLen; }; PieceTreeBase.prototype.getPositionAt = function (offset) { offset = Math.floor(offset); offset = Math.max(0, offset); var x = this.root; var lfCnt = 0; var originalOffset = offset; while (x !== SENTINEL) { if (x.size_left !== 0 && x.size_left >= offset) { x = x.left; } else if (x.size_left + x.piece.length >= offset) { var out = this.getIndexOf(x, offset - x.size_left); lfCnt += x.lf_left + out.index; if (out.index === 0) { var lineStartOffset = this.getOffsetAt(lfCnt + 1, 1); var column = originalOffset - lineStartOffset; return new Position(lfCnt + 1, column + 1); } return new Position(lfCnt + 1, out.remainder + 1); } else { offset -= x.size_left + x.piece.length; lfCnt += x.lf_left + x.piece.lineFeedCnt; if (x.right === SENTINEL) { // last node var lineStartOffset = this.getOffsetAt(lfCnt + 1, 1); var column = originalOffset - offset - lineStartOffset; return new Position(lfCnt + 1, column + 1); } else { x = x.right; } } } return new Position(1, 1); }; PieceTreeBase.prototype.getValueInRange = function (range, eol) { if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) { return ''; } var startPosition = this.nodeAt2(range.startLineNumber, range.startColumn); var endPosition = this.nodeAt2(range.endLineNumber, range.endColumn); var value = this.getValueInRange2(startPosition, endPosition); if (eol) { if (eol !== this._EOL || !this._EOLNormalized) { return value.replace(/\r\n|\r|\n/g, eol); } if (eol === this.getEOL() && this._EOLNormalized) { if (eol === '\r\n') { } return value; } return value.replace(/\r\n|\r|\n/g, eol); } return value; }; PieceTreeBase.prototype.getValueInRange2 = function (startPosition, endPosition) { if (startPosition.node === endPosition.node) { var node = startPosition.node; var buffer_1 = this._buffers[node.piece.bufferIndex].buffer; var startOffset_1 = this.offsetInBuffer(node.piece.bufferIndex, node.piece.start); return buffer_1.substring(startOffset_1 + startPosition.remainder, startOffset_1 + endPosition.remainder); } var x = startPosition.node; var buffer = this._buffers[x.piece.bufferIndex].buffer; var startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); var ret = buffer.substring(startOffset + startPosition.remainder, startOffset + x.piece.length); x = x.next(); while (x !== SENTINEL) { var buffer_2 = this._buffers[x.piece.bufferIndex].buffer; var startOffset_2 = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); if (x === endPosition.node) { ret += buffer_2.substring(startOffset_2, startOffset_2 + endPosition.remainder); break; } else { ret += buffer_2.substr(startOffset_2, x.piece.length); } x = x.next(); } return ret; }; PieceTreeBase.prototype.getLinesContent = function () { return this.getContentOfSubTree(this.root).split(/\r\n|\r|\n/); }; PieceTreeBase.prototype.getLength = function () { return this._length; }; PieceTreeBase.prototype.getLineCount = function () { return this._lineCnt; }; PieceTreeBase.prototype.getLineContent = function (lineNumber) { if (this._lastVisitedLine.lineNumber === lineNumber) { return this._lastVisitedLine.value; } this._lastVisitedLine.lineNumber = lineNumber; if (lineNumber === this._lineCnt) { this._lastVisitedLine.value = this.getLineRawContent(lineNumber); } else if (this._EOLNormalized) { this._lastVisitedLine.value = this.getLineRawContent(lineNumber, this._EOLLength); } else { this._lastVisitedLine.value = this.getLineRawContent(lineNumber).replace(/(\r\n|\r|\n)$/, ''); } return this._lastVisitedLine.value; }; PieceTreeBase.prototype.getLineCharCode = function (lineNumber, index) { var nodePos = this.nodeAt2(lineNumber, index + 1); if (nodePos.remainder === nodePos.node.piece.length) { // the char we want to fetch is at the head of next node. var matchingNode = nodePos.node.next(); if (!matchingNode) { return 0; } var buffer = this._buffers[matchingNode.piece.bufferIndex]; var startOffset = this.offsetInBuffer(matchingNode.piece.bufferIndex, matchingNode.piece.start); return buffer.buffer.charCodeAt(startOffset); } else { var buffer = this._buffers[nodePos.node.piece.bufferIndex]; var startOffset = this.offsetInBuffer(nodePos.node.piece.bufferIndex, nodePos.node.piece.start); var targetOffset = startOffset + nodePos.remainder; return buffer.buffer.charCodeAt(targetOffset); } }; PieceTreeBase.prototype.getLineLength = function (lineNumber) { if (lineNumber === this.getLineCount()) { var startOffset = this.getOffsetAt(lineNumber, 1); return this.getLength() - startOffset; } return this.getOffsetAt(lineNumber + 1, 1) - this.getOffsetAt(lineNumber, 1) - this._EOLLength; }; PieceTreeBase.prototype.findMatchesInNode = function (node, searcher, startLineNumber, startColumn, startCursor, endCursor, searchData, captureMatches, limitResultCount, resultLen, result) { var buffer = this._buffers[node.piece.bufferIndex]; var startOffsetInBuffer = this.offsetInBuffer(node.piece.bufferIndex, node.piece.start); var start = this.offsetInBuffer(node.piece.bufferIndex, startCursor); var end = this.offsetInBuffer(node.piece.bufferIndex, endCursor); var m; // Reset regex to search from the beginning searcher.reset(start); var ret = { 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); var lineFeedCnt = this.getLineFeedCnt(node.piece.bufferIndex, startCursor, ret); var retStartColumn = ret.line === startCursor.line ? ret.column - startCursor.column + startColumn : ret.column + 1; var retEndColumn = retStartColumn + m[0].length; result[resultLen++] = createFindMatch(new Range(startLineNumber + lineFeedCnt, retStartColumn, startLineNumber + lineFeedCnt, retEndColumn), m, captureMatches); if (m.index + m[0].length >= end) { return resultLen; } if (resultLen >= limitResultCount) { return resultLen; } } } while (m); return resultLen; }; PieceTreeBase.prototype.findMatchesLineByLine = function (searchRange, searchData, captureMatches, limitResultCount) { var result = []; var resultLen = 0; var searcher = new Searcher(searchData.wordSeparators, searchData.regex); var startPostion = this.nodeAt2(searchRange.startLineNumber, searchRange.startColumn); if (startPostion === null) { return []; } var endPosition = this.nodeAt2(searchRange.endLineNumber, searchRange.endColumn); if (endPosition === null) { return []; } var start = this.positionInBuffer(startPostion.node, startPostion.remainder); var end = this.positionInBuffer(endPosition.node, endPosition.remainder); if (startPostion.node === endPosition.node) { this.findMatchesInNode(startPostion.node, searcher, searchRange.startLineNumber, searchRange.startColumn, start, end, searchData, captureMatches, limitResultCount, resultLen, result); return result; } var startLineNumber = searchRange.startLineNumber; var currentNode = startPostion.node; while (currentNode !== endPosition.node) { var lineBreakCnt = this.getLineFeedCnt(currentNode.piece.bufferIndex, start, currentNode.piece.end); if (lineBreakCnt >= 1) { // last line break position var lineStarts = this._buffers[currentNode.piece.bufferIndex].lineStarts; var startOffsetInBuffer = this.offsetInBuffer(currentNode.piece.bufferIndex, currentNode.piece.start); var nextLineStartOffset = lineStarts[start.line + lineBreakCnt]; var startColumn_1 = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn : 1; resultLen = this.findMatchesInNode(currentNode, searcher, startLineNumber, startColumn_1, start, this.positionInBuffer(currentNode, nextLineStartOffset - startOffsetInBuffer), searchData, captureMatches, limitResultCount, resultLen, result); if (resultLen >= limitResultCount) { return result; } startLineNumber += lineBreakCnt; } var startColumn_2 = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn - 1 : 0; // search for the remaining content if (startLineNumber === searchRange.endLineNumber) { var text = this.getLineContent(startLineNumber).substring(startColumn_2, searchRange.endColumn - 1); resultLen = this._findMatchesInLine(searchData, searcher, text, searchRange.endLineNumber, startColumn_2, resultLen, result, captureMatches, limitResultCount); return result; } resultLen = this._findMatchesInLine(searchData, searcher, this.getLineContent(startLineNumber).substr(startColumn_2), startLineNumber, startColumn_2, 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) { var startColumn_3 = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn - 1 : 0; var text = this.getLineContent(startLineNumber).substring(startColumn_3, searchRange.endColumn - 1); resultLen = this._findMatchesInLine(searchData, searcher, text, searchRange.endLineNumber, startColumn_3, resultLen, result, captureMatches, limitResultCount); return result; } var startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn : 1; resultLen = this.findMatchesInNode(endPosition.node, searcher, startLineNumber, startColumn, start, end, searchData, captureMatches, limitResultCount, resultLen, result); return result; }; PieceTreeBase.prototype._findMatchesInLine = function (searchData, searcher, text, lineNumber, deltaOffset, resultLen, result, captureMatches, limitResultCount) { var wordSeparators = searchData.wordSeparators; if (!captureMatches && searchData.simpleSearch) { var searchString = searchData.simpleSearch; var searchStringLen = searchString.length; var textLength = text.length; var 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; } var m; // 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 PieceTreeBase.prototype.insert = function (offset, value, eolNormalized) { if (eolNormalized === void 0) { eolNormalized = false; } this._EOLNormalized = this._EOLNormalized && eolNormalized; this._lastVisitedLine.lineNumber = 0; this._lastVisitedLine.value = ''; if (this.root !== SENTINEL) { var _a = this.nodeAt(offset), node = _a.node, remainder = _a.remainder, nodeStartOffset = _a.nodeStartOffset; var piece = node.piece; var bufferIndex = piece.bufferIndex; var insertPosInBuffer = this.positionInBuffer(node, remainder); if (node.piece.bufferIndex === 0 && piece.end.line === this._lastChangeBufferPos.line && piece.end.column === this._lastChangeBufferPos.column && (nodeStartOffset + piece.length === offset) && value.length < AverageBufferSize) { // changed buffer this.appendToNode(node, value); this.computeBufferMetadata(); return; } if (nodeStartOffset === offset) { this.insertContentToNodeLeft(value, node); this._searchCache.valdiate(offset); } else if (nodeStartOffset + node.piece.length > offset) { // we are inserting into the middle of a node. var nodesToDel = []; var newRightPiece = new Piece(piece.bufferIndex, insertPosInBuffer, piece.end, this.getLineFeedCnt(piece.bufferIndex, insertPosInBuffer, piece.end), this.offsetInBuffer(bufferIndex, piece.end) - this.offsetInBuffer(bufferIndex, insertPosInBuffer)); if (this.shouldCheckCRLF() && this.endWithCR(value)) { var headOfRight = this.nodeCharCodeAt(node, remainder); if (headOfRight === 10 /** \n */) { var newStart = { line: newRightPiece.start.line + 1, column: 0 }; newRightPiece = new Piece(newRightPiece.bufferIndex, newStart, newRightPiece.end, this.getLineFeedCnt(newRightPiece.bufferIndex, newStart, newRightPiece.end), newRightPiece.length - 1); value += '\n'; } } // reuse node for content before insertion point. if (this.shouldCheckCRLF() && this.startWithLF(value)) { var tailOfLeft = this.nodeCharCodeAt(node, remainder - 1); if (tailOfLeft === 13 /** \r */) { var previousPos = this.positionInBuffer(node, remainder - 1); this.deleteNodeTail(node, previousPos); value = '\r' + value; if (node.piece.length === 0) { nodesToDel.push(node); } } else { this.deleteNodeTail(node, insertPosInBuffer); } } else { this.deleteNodeTail(node, insertPosInBuffer); } var newPieces = this.createNewPieces(value); if (newRightPiece.length > 0) { this.rbInsertRight(node, newRightPiece); } var tmpNode = node; for (var k = 0; k < newPieces.length; k++) { tmpNode = this.rbInsertRight(tmpNode, newPieces[k]); } this.deleteNodes(nodesToDel); } else { this.insertContentToNodeRight(value, node); } } else { // insert new node var pieces = this.createNewPieces(value); var node = this.rbInsertLeft(null, pieces[0]); for (var k = 1; k < pieces.length; k++) { node = this.rbInsertRight(node, pieces[k]); } } // todo, this is too brutal. Total line feed count should be updated the same way as lf_left. this.computeBufferMetadata(); }; PieceTreeBase.prototype.delete = function (offset, cnt) { this._lastVisitedLine.lineNumber = 0; this._lastVisitedLine.value = ''; if (cnt <= 0 || this.root === SENTINEL) { return; } var startPosition = this.nodeAt(offset); var endPosition = this.nodeAt(offset + cnt); var startNode = startPosition.node; var endNode = endPosition.node; if (startNode === endNode) { var startSplitPosInBuffer_1 = this.positionInBuffer(startNode, startPosition.remainder); var endSplitPosInBuffer_1 = this.positionInBuffer(startNode, endPosition.remainder); if (startPosition.nodeStartOffset === offset) { if (cnt === startNode.piece.length) { // delete node var next = startNode.next(); rbDelete(this, startNode); this.validateCRLFWithPrevNode(next); this.computeBufferMetadata(); return; } this.deleteNodeHead(startNode, endSplitPosInBuffer_1); this._searchCache.valdiate(offset); this.validateCRLFWithPrevNode(startNode); this.computeBufferMetadata(); return; } if (startPosition.nodeStartOffset + startNode.piece.length === offset + cnt) { this.deleteNodeTail(startNode, startSplitPosInBuffer_1); this.validateCRLFWithNextNode(startNode); this.computeBufferMetadata(); return; } // delete content in the middle, this node will be splitted to nodes this.shrinkNode(startNode, startSplitPosInBuffer_1, endSplitPosInBuffer_1); this.computeBufferMetadata(); return; } var nodesToDel = []; var startSplitPosInBuffer = this.positionInBuffer(startNode, startPosition.remainder); this.deleteNodeTail(startNode, startSplitPosInBuffer); this._searchCache.valdiate(offset); if (startNode.piece.length === 0) { nodesToDel.push(startNode); } // update last touched node var endSplitPosInBuffer = this.positionInBuffer(endNode, endPosition.remainder); this.deleteNodeHead(endNode, endSplitPosInBuffer); if (endNode.piece.length === 0) { nodesToDel.push(endNode); } // delete nodes in between var secondNode = startNode.next(); for (var node = secondNode; node !== SENTINEL && node !== endNode; node = node.next()) { nodesToDel.push(node); } var prev = startNode.piece.length === 0 ? startNode.prev() : startNode; this.deleteNodes(nodesToDel); this.validateCRLFWithNextNode(prev); this.computeBufferMetadata(); }; PieceTreeBase.prototype.insertContentToNodeLeft = function (value, node) { // we are inserting content to the beginning of node var nodesToDel = []; if (this.shouldCheckCRLF() && this.endWithCR(value) && this.startWithLF(node)) { // move `\n` to new node. var piece = node.piece; var newStart = { line: piece.start.line + 1, column: 0 }; var nPiece = new Piece(piece.bufferIndex, newStart, piece.end, this.getLineFeedCnt(piece.bufferIndex, newStart, piece.end), piece.length - 1); node.piece = nPiece; value += '\n'; updateTreeMetadata(this, node, -1, -1); if (node.piece.length === 0) { nodesToDel.push(node); } } var newPieces = this.createNewPieces(value); var newNode = this.rbInsertLeft(node, newPieces[newPieces.length - 1]); for (var k = newPieces.length - 2; k >= 0; k--) { newNode = this.rbInsertLeft(newNode, newPieces[k]); } this.validateCRLFWithPrevNode(newNode); this.deleteNodes(nodesToDel); }; PieceTreeBase.prototype.insertContentToNodeRight = function (value, node) { // we are inserting to the right of this node. if (this.adjustCarriageReturnFromNext(value, node)) { // move \n to the new node. value += '\n'; } var newPieces = this.createNewPieces(value); var newNode = this.rbInsertRight(node, newPieces[0]); var tmpNode = newNode; for (var k = 1; k < newPieces.length; k++) { tmpNode = this.rbInsertRight(tmpNode, newPieces[k]); } this.validateCRLFWithPrevNode(newNode); }; PieceTreeBase.prototype.positionInBuffer = function (node, remainder, ret) { var piece = node.piece; var bufferIndex = node.piece.bufferIndex; var lineStarts = this._buffers[bufferIndex].lineStarts; var startOffset = lineStarts[piece.start.line] + piece.start.column; var offset = startOffset + remainder; // binary search offset between startOffset and endOffset var low = piece.start.line; var high = piece.end.line; var mid = 0; var midStop = 0; var midStart = 0; while (low <= high) { mid = low + ((high - low) / 2) | 0; midStart = lineStarts[mid]; if (mid === high) { break; } midStop = lineStarts[mid + 1]; if (offset < midStart) { high = mid - 1; } else if (offset >= midStop) { low = mid + 1; } else { break; } } if (ret) { ret.line = mid; ret.column = offset - midStart; return null; } return { line: mid, column: offset - midStart }; }; PieceTreeBase.prototype.getLineFeedCnt = function (bufferIndex, start, end) { // we don't need to worry about start: abc\r|\n, or abc|\r, or abc|\n, or abc|\r\n doesn't change the fact that, there is one line break after start. // now let's take care of end: abc\r|\n, if end is in between \r and \n, we need to add line feed count by 1 if (end.column === 0) { return end.line - start.line; } var lineStarts = this._buffers[bufferIndex].lineStarts; if (end.line === lineStarts.length - 1) { // it means, there is no \n after end, otherwise, there will be one more lineStart. return end.line - start.line; } var nextLineStartOffset = lineStarts[end.line + 1]; var endOffset = lineStarts[end.line] + end.column; if (nextLineStartOffset > endOffset + 1) { // there are more than 1 character after end, which means it can't be \n return end.line - start.line; } // endOffset + 1 === nextLineStartOffset // character at endOffset is \n, so we check the character before first // if character at endOffset is \r, end.column is 0 and we can't get here. var previousCharOffset = endOffset - 1; // end.column > 0 so it's okay. var buffer = this._buffers[bufferIndex].buffer; if (buffer.charCodeAt(previousCharOffset) === 13) { return end.line - start.line + 1; } else { return end.line - start.line; } }; PieceTreeBase.prototype.offsetInBuffer = function (bufferIndex, cursor) { var lineStarts = this._buffers[bufferIndex].lineStarts; return lineStarts[cursor.line] + cursor.column; }; PieceTreeBase.prototype.deleteNodes = function (nodes) { for (var i = 0; i < nodes.length; i++) { rbDelete(this, nodes[i]); } }; PieceTreeBase.prototype.createNewPieces = function (text) { if (text.length > AverageBufferSize) { // the content is large, operations like substring, charCode becomes slow // so here we split it into smaller chunks, just like what we did for CR/LF normalization var newPieces = []; while (text.length > AverageBufferSize) { var lastChar = text.charCodeAt(AverageBufferSize - 1); var splitText = void 0; if (lastChar === 13 /* CarriageReturn */ || (lastChar >= 0xd800 && lastChar <= 0xdbff)) { // last character is \r or a high surrogate => keep it back splitText = text.substring(0, AverageBufferSize - 1); text = text.substring(AverageBufferSize - 1); } else { splitText = text.substring(0, AverageBufferSize); text = text.substring(AverageBufferSize); } var lineStarts_1 = createLineStartsFast(splitText); newPieces.push(new Piece(this._buffers.length, /* buffer index */ { line: 0, column: 0 }, { line: lineStarts_1.length - 1, column: splitText.length - lineStarts_1[lineStarts_1.length - 1] }, lineStarts_1.length - 1, splitText.length)); this._buffers.push(new StringBuffer(splitText, lineStarts_1)); } var lineStarts_2 = createLineStartsFast(text); newPieces.push(new Piece(this._buffers.length, /* buffer index */ { line: 0, column: 0 }, { line: lineStarts_2.length - 1, column: text.length - lineStarts_2[lineStarts_2.length - 1] }, lineStarts_2.length - 1, text.length)); this._buffers.push(new StringBuffer(text, lineStarts_2)); return newPieces; } var startOffset = this._buffers[0].buffer.length; var lineStarts = createLineStartsFast(text, false); var start = this._lastChangeBufferPos; if (this._buffers[0].lineStarts[this._buffers[0].lineStarts.length - 1] === startOffset && startOffset !== 0 && this.startWithLF(text) && this.endWithCR(this._buffers[0].buffer) // todo, we can check this._lastChangeBufferPos's column as it's the last one ) { this._lastChangeBufferPos = { line: this._lastChangeBufferPos.line, column: this._lastChangeBufferPos.column + 1 }; start = this._lastChangeBufferPos; for (var i = 0; i < lineStarts.length; i++) { lineStarts[i] += startOffset + 1; } this._buffers[0].lineStarts = this._buffers[0].lineStarts.concat(lineStarts.slice(1)); this._buffers[0].buffer += '_' + text; startOffset += 1; } else { if (startOffset !== 0) { for (var i = 0; i < lineStarts.length; i++) { lineStarts[i] += startOffset; } } this._buffers[0].lineStarts = this._buffers[0].lineStarts.concat(lineStarts.slice(1)); this._buffers[0].buffer += text; } var endOffset = this._buffers[0].buffer.length; var endIndex = this._buffers[0].lineStarts.length - 1; var endColumn = endOffset - this._buffers[0].lineStarts[endIndex]; var endPos = { line: endIndex, column: endColumn }; var newPiece = new Piece(0, /** todo@peng */ start, endPos, this.getLineFeedCnt(0, start, endPos), endOffset - startOffset); this._lastChangeBufferPos = endPos; return [newPiece]; }; PieceTreeBase.prototype.getLineRawContent = function (lineNumber, endOffset) { if (endOffset === void 0) { endOffset = 0; } var x = this.root; var ret = ''; var cache = this._searchCache.get2(lineNumber); if (cache) { x = cache.node; var prevAccumualtedValue = this.getAccumulatedValue(x, lineNumber - cache.nodeStartLineNumber - 1); var buffer = this._buffers[x.piece.bufferIndex].buffer; var startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); if (cache.nodeStartLineNumber + x.piece.lineFeedCnt === lineNumber) { ret = buffer.substring(startOffset + prevAccumualtedValue, startOffset + x.piece.length); } else { var accumualtedValue = this.getAccumulatedValue(x, lineNumber - cache.nodeStartLineNumber); return buffer.substring(startOffset + prevAccumualtedValue, startOffset + accumualtedValue - endOffset); } } else { var nodeStartOffset = 0; var originalLineNumber = lineNumber; while (x !== SENTINEL) { if (x.left !== SENTINEL && x.lf_left >= lineNumber - 1) { x = x.left; } else if (x.lf_left + x.piece.lineFeedCnt > lineNumber - 1) { var prevAccumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 2); var accumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 1); var buffer = this._buffers[x.piece.bufferIndex].buffer; var startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); nodeStartOffset += x.size_left; this._searchCache.set({ node: x, nodeStartOffset: nodeStartOffset, nodeStartLineNumber: originalLineNumber - (lineNumber - 1 - x.lf_left) }); return buffer.substring(startOffset + prevAccumualtedValue, startOffset + accumualtedValue - endOffset); } else if (x.lf_left + x.piece.lineFeedCnt === lineNumber - 1) { var prevAccumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 2); var buffer = this._buffers[x.piece.bufferIndex].buffer; var startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); ret = buffer.substring(startOffset + prevAccumualtedValue, startOffset + x.piece.length); break; } else { lineNumber -= x.lf_left + x.piece.lineFeedCnt; nodeStartOffset += x.size_left + x.piece.length; x = x.right; } } } // search in order, to find the node contains end column x = x.next(); while (x !== SENTINEL) { var buffer = this._buffers[x.piece.bufferIndex].buffer; if (x.piece.lineFeedCnt > 0) { var accumualtedValue = this.getAccumulatedValue(x, 0); var startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); ret += buffer.substring(startOffset, startOffset + accumualtedValue - endOffset); return ret; } else { var startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); ret += buffer.substr(startOffset, x.piece.length); } x = x.next(); } return ret; }; PieceTreeBase.prototype.computeBufferMetadata = function () { var x = this.root; var lfCnt = 1; var len = 0; while (x !== SENTINEL) { lfCnt += x.lf_left + x.piece.lineFeedCnt; len += x.size_left + x.piece.length; x = x.right; } this._lineCnt = lfCnt; this._length = len; this._searchCache.valdiate(this._length); }; // #region node operations PieceTreeBase.prototype.getIndexOf = function (node, accumulatedValue) { var piece = node.piece; var pos = this.positionInBuffer(node, accumulatedValue); var lineCnt = pos.line - piece.start.line; if (this.offsetInBuffer(piece.bufferIndex, piece.end) - this.offsetInBuffer(piece.bufferIndex, piece.start) === accumulatedValue) { // we are checking the end of this node, so a CRLF check is necessary. var realLineCnt = this.getLineFeedCnt(node.piece.bufferIndex, piece.start, pos); if (realLineCnt !== lineCnt) { // aha yes, CRLF return { index: realLineCnt, remainder: 0 }; } } return { index: lineCnt, remainder: pos.column }; }; PieceTreeBase.prototype.getAccumulatedValue = function (node, index) { if (index < 0) { return 0; } var piece = node.piece; var lineStarts = this._buffers[piece.bufferIndex].lineStarts; var expectedLineStartIndex = piece.start.line + index + 1; if (expectedLineStartIndex > piece.end.line) { return lineStarts[piece.end.line] + piece.end.column - lineStarts[piece.start.line] - piece.start.column; } else { return lineStarts[expectedLineStartIndex] - lineStarts[piece.start.line] - piece.start.column; } }; PieceTreeBase.prototype.deleteNodeTail = function (node, pos) { var piece = node.piece; var originalLFCnt = piece.lineFeedCnt; var originalEndOffset = this.offsetInBuffer(piece.bufferIndex, piece.end); var newEnd = pos; var newEndOffset = this.offsetInBuffer(piece.bufferIndex, newEnd); var newLineFeedCnt = this.getLineFeedCnt(piece.bufferIndex, piece.start, newEnd); var lf_delta = newLineFeedCnt - originalLFCnt; var size_delta = newEndOffset - originalEndOffset; var newLength = piece.length + size_delta; node.piece = new Piece(piece.bufferIndex, piece.start, newEnd, newLineFeedCnt, newLength); updateTreeMetadata(this, node, size_delta, lf_delta); }; PieceTreeBase.prototype.deleteNodeHead = function (node, pos) { var piece = node.piece; var originalLFCnt = piece.lineFeedCnt; var originalStartOffset = this.offsetInBuffer(piece.bufferIndex, piece.start); var newStart = pos; var newLineFeedCnt = this.getLineFeedCnt(piece.bufferIndex, newStart, piece.end); var newStartOffset = this.offsetInBuffer(piece.bufferIndex, newStart); var lf_delta = newLineFeedCnt - originalLFCnt; var size_delta = originalStartOffset - newStartOffset; var newLength = piece.length + size_delta; node.piece = new Piece(piece.bufferIndex, newStart, piece.end, newLineFeedCnt, newLength); updateTreeMetadata(this, node, size_delta, lf_delta); }; PieceTreeBase.prototype.shrinkNode = function (node, start, end) { var piece = node.piece; var originalStartPos = piece.start; var originalEndPos = piece.end; // old piece, originalStartPos, start var oldLength = piece.length; var oldLFCnt = piece.lineFeedCnt; var newEnd = start; var newLineFeedCnt = this.getLineFeedCnt(piece.bufferIndex, piece.start, newEnd); var newLength = this.offsetInBuffer(piece.bufferIndex, start) - this.offsetInBuffer(piece.bufferIndex, originalStartPos); node.piece = new Piece(piece.bufferIndex, piece.start, newEnd, newLineFeedCnt, newLength); updateTreeMetadata(this, node, newLength - oldLength, newLineFeedCnt - oldLFCnt); // new right piece, end, originalEndPos var newPiece = new Piece(piece.bufferIndex, end, originalEndPos, this.getLineFeedCnt(piece.bufferIndex, end, originalEndPos), this.offsetInBuffer(piece.bufferIndex, originalEndPos) - this.offsetInBuffer(piece.bufferIndex, end)); var newNode = this.rbInsertRight(node, newPiece); this.validateCRLFWithPrevNode(newNode); }; PieceTreeBase.prototype.appendToNode = function (node, value) { if (this.adjustCarriageReturnFromNext(value, node)) { value += '\n'; } var hitCRLF = this.shouldCheckCRLF() && this.startWithLF(value) && this.endWithCR(node); var startOffset = this._buffers[0].buffer.length; this._buffers[0].buffer += value; var lineStarts = createLineStartsFast(value, false); for (var i = 0; i < lineStarts.length; i++) { lineStarts[i] += startOffset; } if (hitCRLF) { var prevStartOffset = this._buffers[0].lineStarts[this._buffers[0].lineStarts.length - 2]; this._buffers[0].lineStarts.pop(); // _lastChangeBufferPos is already wrong this._lastChangeBufferPos = { line: this._lastChangeBufferPos.line - 1, column: startOffset - prevStartOffset }; } this._buffers[0].lineStarts = this._buffers[0].lineStarts.concat(lineStarts.slice(1)); var endIndex = this._buffers[0].lineStarts.length - 1; var endColumn = this._buffers[0].buffer.length - this._buffers[0].lineStarts[endIndex]; var newEnd = { line: endIndex, column: endColumn }; var newLength = node.piece.length + value.length; var oldLineFeedCnt = node.piece.lineFeedCnt; var newLineFeedCnt = this.getLineFeedCnt(0, node.piece.start, newEnd); var lf_delta = newLineFeedCnt - oldLineFeedCnt; node.piece = new Piece(node.piece.bufferIndex, node.piece.start, newEnd, newLineFeedCnt, newLength); this._lastChangeBufferPos = newEnd; updateTreeMetadata(this, node, value.length, lf_delta); }; PieceTreeBase.prototype.nodeAt = function (offset) { var x = this.root; var cache = this._searchCache.get(offset); if (cache) { return { node: cache.node, nodeStartOffset: cache.nodeStartOffset, remainder: offset - cache.nodeStartOffset }; } var nodeStartOffset = 0; while (x !== SENTINEL) { if (x.size_left > offset) { x = x.left; } else if (x.size_left + x.piece.length >= offset) { nodeStartOffset += x.size_left; var ret = { node: x, remainder: offset - x.size_left, nodeStartOffset: nodeStartOffset }; this._