Refactor TreeNode method signature

- add some test cases for changed code
This commit is contained in:
Comonad 2021-03-31 14:08:50 +08:00
parent 2d5f0e4953
commit afe4caed26
3 changed files with 103 additions and 50 deletions

View file

@ -7,7 +7,7 @@ import { CharCode } from 'vs/base/common/charCode';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { FindMatch, ITextSnapshot } from 'vs/editor/common/model';
import { NodeColor, SENTINEL, TreeNode, fixInsert, leftest, rbDelete, righttest, updateTreeMetadata } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase';
import { NodeColor, SENTINEL, TreeNode, fixInsert, leftmost, rbDelete, rightmost, updateTreeMetadata, createTreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase';
import { SearchData, Searcher, createFindMatch, isValidMatch } from 'vs/editor/common/model/textModelSearch';
// const lfRegex = new RegExp(/\r\n|\r|\n/g);
@ -294,7 +294,7 @@ export class PieceTreeBase {
let lastNode: TreeNode | null = null;
for (let i = 0, len = chunks.length; i < len; i++) {
if (chunks[i].buffer.length > 0) {
if (chunks[i].buffer.length > 0) { // skip emty buffers
if (!chunks[i].lineStarts) {
chunks[i].lineStarts = createLineStartsFast(chunks[i].buffer);
}
@ -307,7 +307,11 @@ export class PieceTreeBase {
chunks[i].buffer.length
);
this._buffers.push(chunks[i]);
lastNode = this.rbInsertRight(lastNode, piece);
if (lastNode === null) {
lastNode = this.initRootNode(piece);
} else {
lastNode = this.rbInsertRight(lastNode, piece);
}
}
}
@ -910,7 +914,7 @@ export class PieceTreeBase {
} else {
// insert new node
let pieces = this.createNewPieces(value);
let node = this.rbInsertLeft(null, pieces[0]);
let node = this.initRootNode(pieces[0]);
for (let k = 1; k < pieces.length; k++) {
node = this.rbInsertRight(node, pieces[k]);
@ -1762,20 +1766,14 @@ export class PieceTreeBase {
return callback(node) && this.iterate(node.right, callback);
}
private getNodeContent(node: TreeNode) {
private getNodeContent(node: TreeNode): string {
if (node === SENTINEL) {
return '';
}
let buffer = this._buffers[node.piece.bufferIndex];
let currentContent;
let piece = node.piece;
let startOffset = this.offsetInBuffer(piece.bufferIndex, piece.start);
let endOffset = this.offsetInBuffer(piece.bufferIndex, piece.end);
currentContent = buffer.buffer.substring(startOffset, endOffset);
return currentContent;
return this.getPieceContent(node.piece);
}
getPieceContent(piece: Piece) {
getPieceContent(piece: Piece): string {
let buffer = this._buffers[piece.bufferIndex];
let startOffset = this.offsetInBuffer(piece.bufferIndex, piece.start);
let endOffset = this.offsetInBuffer(piece.bufferIndex, piece.end);
@ -1783,30 +1781,26 @@ export class PieceTreeBase {
return currentContent;
}
private initRootNode(p: Piece): TreeNode {
return this.root = createTreeNode(p, NodeColor.Black);
}
/**
* node node
* / \ / \
* a b <---- a b
* /
* z
* Require this.root !== SENTINEL
*/
private rbInsertRight(node: TreeNode | null, p: Piece): TreeNode {
let z = new TreeNode(p, NodeColor.Red);
z.left = SENTINEL;
z.right = SENTINEL;
z.parent = SENTINEL;
z.size_left = 0;
z.lf_left = 0;
private rbInsertRight(node: TreeNode, p: Piece): TreeNode {
let z = createTreeNode(p, NodeColor.Red);
let x = this.root;
if (x === SENTINEL) {
this.root = z;
z.color = NodeColor.Black;
} else if (node!.right === SENTINEL) {
if (node!.right === SENTINEL) {
node!.right = z;
z.parent = node!;
} else {
let nextNode = leftest(node!.right);
let nextNode = leftmost(node!.right);
nextNode.left = z;
z.parent = nextNode;
}
@ -1821,23 +1815,16 @@ export class PieceTreeBase {
* a b ----> a b
* \
* z
* Require this.root !== SENTINEL
*/
private rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode {
let z = new TreeNode(p, NodeColor.Red);
z.left = SENTINEL;
z.right = SENTINEL;
z.parent = SENTINEL;
z.size_left = 0;
z.lf_left = 0;
private rbInsertLeft(node: TreeNode, p: Piece): TreeNode {
let z = createTreeNode(p, NodeColor.Red);
if (this.root === SENTINEL) {
this.root = z;
z.color = NodeColor.Black;
} else if (node!.left === SENTINEL) {
if (node!.left === SENTINEL) {
node!.left = z;
z.parent = node!;
} else {
let prevNode = righttest(node!.left); // a
let prevNode = rightmost(node!.left); // a
prevNode.right = z;
z.parent = prevNode;
}

View file

@ -14,7 +14,7 @@ export class TreeNode {
// Piece
piece: Piece;
size_left: number; // size of the left subtree (not inorder)
lf_left: number; // line feeds cnt in the left subtree (not in order)
lf_left: number; // line feeds cnt in the left subtree (not inorder)
constructor(piece: Piece, color: NodeColor) {
this.piece = piece;
@ -28,7 +28,7 @@ export class TreeNode {
public next(): TreeNode {
if (this.right !== SENTINEL) {
return leftest(this.right);
return leftmost(this.right);
}
let node: TreeNode = this;
@ -50,7 +50,7 @@ export class TreeNode {
public prev(): TreeNode {
if (this.left !== SENTINEL) {
return righttest(this.left);
return rightmost(this.left);
}
let node: TreeNode = this;
@ -88,14 +88,14 @@ SENTINEL.left = SENTINEL;
SENTINEL.right = SENTINEL;
SENTINEL.color = NodeColor.Black;
export function leftest(node: TreeNode): TreeNode {
export function leftmost(node: TreeNode): TreeNode {
while (node.left !== SENTINEL) {
node = node.left;
}
return node;
}
export function righttest(node: TreeNode): TreeNode {
export function rightmost(node: TreeNode): TreeNode {
while (node.right !== SENTINEL) {
node = node.right;
}
@ -103,19 +103,25 @@ export function righttest(node: TreeNode): TreeNode {
}
export function calculateSize(node: TreeNode): number {
if (node === SENTINEL) {
return 0;
let sum = 0;
while (node !== SENTINEL) {
sum += node.size_left + node.piece.length;
node = node.right;
}
return node.size_left + node.piece.length + calculateSize(node.right);
return sum;
}
export function calculateLF(node: TreeNode): number {
if (node === SENTINEL) {
return 0;
let sum = 0;
while (node !== SENTINEL) {
sum += node.lf_left + node.piece.lineFeedCnt;
node = node.right;
}
return node.lf_left + node.piece.lineFeedCnt + calculateLF(node.right);
return sum;
}
export function resetSentinel(): void {
@ -180,7 +186,7 @@ export function rbDelete(tree: PieceTreeBase, z: TreeNode) {
y = z;
x = y.left;
} else {
y = leftest(z.right);
y = leftmost(z.right);
x = y.right;
}
@ -375,6 +381,13 @@ export function fixInsert(tree: PieceTreeBase, x: TreeNode) {
tree.root.color = NodeColor.Black;
}
export function createTreeNode(p: Piece, color: NodeColor): TreeNode {
const treeNode = new TreeNode(p, color);
treeNode.parent = treeNode.left = treeNode.right = SENTINEL;
treeNode.size_left = treeNode.lf_left = 0;
return treeNode;
}
export function updateTreeMetadata(tree: PieceTreeBase, x: TreeNode, delta: number, lineFeedCntDelta: number): void {
// node length change or line feed count change
while (x !== tree.root && x !== SENTINEL) {

View file

@ -171,6 +171,13 @@ function assertTreeInvariants(T: PieceTreeBase): void {
assertValidTree(T);
}
function treeSize(n: TreeNode): number {
if (n === SENTINEL) {
return 0;
}
return treeSize(n.left) + treeSize(n.right) + 1;
}
function depth(n: TreeNode): number {
if (n === SENTINEL) {
// The leafs are black
@ -212,6 +219,52 @@ function assertValidTree(T: PieceTreeBase): void {
//#endregion
suite('rbtree init and inserts', () => {
test('init root node', () => {
let pieceTree = createTextBuffer([
'Init text.'
]); // init root node in ctor
assertTreeInvariants(pieceTree);
assert.strictEqual(treeSize(pieceTree.root), 1);
let pieceTree2 = new PieceTreeBase([], '\r\n', false); // root node is now SENTINEL
assert.strictEqual(pieceTree2.root, SENTINEL);
pieceTree2.insert(0, 'Init text'); // init root node
assertTreeInvariants(pieceTree2);
assert.strictEqual(treeSize(pieceTree2.root), 1);
});
test('insert left', () => {
let pieceTree = createTextBuffer([
'Test text 2.'
]);
pieceTree.insert(0, 'Test text 1.'); // insert left to the root node
assertTreeInvariants(pieceTree);
assert.strictEqual(treeSize(pieceTree.root), 2);
pieceTree.insert(0, 'Test text 0.');
assertTreeInvariants(pieceTree);
assert.strictEqual(treeSize(pieceTree.root), 3);
assert.strictEqual(pieceTree.getLinesRawContent(), 'Test text 0.Test text 1.Test text 2.');
});
test('insert right', () => {
let pieceTree = createTextBuffer([
'Test text 0.', 'Test text 1.', 'Test text 2.'
]); // init root and 2 rbInsertRight
assertTreeInvariants(pieceTree);
assert.strictEqual(treeSize(pieceTree.root), 3);
assert.strictEqual(pieceTree.getLinesRawContent(), 'Test text 0.Test text 1.Test text 2.');
pieceTree.insert(pieceTree.getLength(), 'Test text 3.'); // insert rigth to the last node
assertTreeInvariants(pieceTree);
assert.strictEqual(treeSize(pieceTree.root), 4);
assert.strictEqual(pieceTree.getLinesRawContent(), 'Test text 0.Test text 1.Test text 2.Test text 3.');
});
});
suite('inserts and deletes', () => {
test('basic insert/delete', () => {
let pieceTable = createTextBuffer([