simple-text-buffer
Version:
A version of Atom's text-buffer with marker layers and file support removed
588 lines (542 loc) • 22.4 kB
JavaScript
(function() {
var BRANCHING_THRESHOLD, ChangeIterator, Leaf, Node, Patch, Point, RegionIterator, isEmpty, last,
slice = [].slice;
Point = require("./point");
last = function(array) {
return array[array.length - 1];
};
isEmpty = function(node) {
return node.inputExtent.isZero() && node.outputExtent.isZero();
};
BRANCHING_THRESHOLD = 3;
Node = (function() {
function Node(children) {
this.children = children;
this.calculateExtent();
}
Node.prototype.splice = function(childIndex, splitChildren) {
var inputOffset, leftMergeIndex, outputOffset, ref, ref1, rightMergeIndex, rightNeighbor, spliceChild, splitIndex, splitNodes;
spliceChild = this.children[childIndex];
leftMergeIndex = rightMergeIndex = childIndex;
if (splitChildren != null) {
(ref = this.children).splice.apply(ref, [childIndex, 1].concat(slice.call(splitChildren)));
childIndex += splitChildren.indexOf(spliceChild);
rightMergeIndex += splitChildren.length - 1;
}
if (rightNeighbor = this.children[rightMergeIndex + 1]) {
this.children[rightMergeIndex].merge(rightNeighbor);
if (isEmpty(rightNeighbor)) {
this.children.splice(rightMergeIndex + 1, 1);
}
}
splitIndex = Math.ceil(this.children.length / BRANCHING_THRESHOLD);
if (splitIndex > 1) {
if (childIndex < splitIndex) {
splitNodes = [this, new Node(this.children.splice(splitIndex))];
} else {
splitNodes = [new Node(this.children.splice(0, splitIndex)), this];
childIndex -= splitIndex;
}
}
ref1 = this.calculateExtent(childIndex), inputOffset = ref1.inputOffset, outputOffset = ref1.outputOffset;
return {
splitNodes: splitNodes,
inputOffset: inputOffset,
outputOffset: outputOffset,
childIndex: childIndex
};
};
Node.prototype.merge = function(rightNeighbor) {
var childMerge, ref, ref1, result;
childMerge = (ref = last(this.children)) != null ? ref.merge(rightNeighbor.children[0]) : void 0;
if (isEmpty(rightNeighbor.children[0])) {
rightNeighbor.children.shift();
}
if (this.children.length + rightNeighbor.children.length <= BRANCHING_THRESHOLD) {
this.inputExtent = this.inputExtent.traverse(rightNeighbor.inputExtent);
this.outputExtent = this.outputExtent.traverse(rightNeighbor.outputExtent);
(ref1 = this.children).push.apply(ref1, rightNeighbor.children);
result = {
inputExtent: rightNeighbor.inputExtent,
outputExtent: rightNeighbor.outputExtent
};
rightNeighbor.inputExtent = rightNeighbor.outputExtent = Point.ZERO;
return result;
} else if (childMerge != null) {
this.inputExtent = this.inputExtent.traverse(childMerge.inputExtent);
this.outputExtent = this.outputExtent.traverse(childMerge.outputExtent);
rightNeighbor.inputExtent = rightNeighbor.inputExtent.traversalFrom(childMerge.inputExtent);
rightNeighbor.outputExtent = rightNeighbor.outputExtent.traversalFrom(childMerge.outputExtent);
return childMerge;
}
};
Node.prototype.calculateExtent = function(childIndex) {
var child, i, j, len, ref, result;
result = {
inputOffset: null,
outputOffset: null
};
this.inputExtent = Point.ZERO;
this.outputExtent = Point.ZERO;
ref = this.children;
for (i = j = 0, len = ref.length; j < len; i = ++j) {
child = ref[i];
if (i === childIndex) {
result.inputOffset = this.inputExtent;
result.outputOffset = this.outputExtent;
}
this.inputExtent = this.inputExtent.traverse(child.inputExtent);
this.outputExtent = this.outputExtent.traverse(child.outputExtent);
}
return result;
};
Node.prototype.toString = function(indentLevel) {
var i, indent, j, ref;
if (indentLevel == null) {
indentLevel = 0;
}
indent = "";
for (i = j = 0, ref = indentLevel; j < ref; i = j += 1) {
indent += " ";
}
return indent + "[Node " + this.inputExtent + " " + this.outputExtent + "]\n" + (this.children.map(function(c) {
return c.toString(indentLevel + 2);
}).join("\n"));
};
return Node;
})();
Leaf = (function() {
function Leaf(inputExtent1, outputExtent, content1) {
this.inputExtent = inputExtent1;
this.outputExtent = outputExtent;
this.content = content1;
}
Leaf.prototype.insert = function(inputOffset, outputOffset, newInputExtent, newOutputExtent, newContent) {
var inputExtentAfterOffset, outputExtentAfterOffset, splitNodes;
inputExtentAfterOffset = this.inputExtent.traversalFrom(inputOffset);
outputExtentAfterOffset = this.outputExtent.traversalFrom(outputOffset);
if (this.content != null) {
this.inputExtent = inputOffset.traverse(newInputExtent).traverse(inputExtentAfterOffset);
this.outputExtent = outputOffset.traverse(newOutputExtent).traverse(outputExtentAfterOffset);
this.content = this.content.slice(0, outputOffset.column) + newContent + this.content.slice(outputOffset.column);
inputOffset = inputOffset.traverse(newInputExtent);
outputOffset = outputOffset.traverse(newOutputExtent);
} else if (newInputExtent.isPositive() || newOutputExtent.isPositive()) {
splitNodes = [];
if (outputOffset.isPositive()) {
splitNodes.push(new Leaf(inputOffset, outputOffset, null));
}
this.inputExtent = newInputExtent;
this.outputExtent = newOutputExtent;
this.content = newContent;
splitNodes.push(this);
if (outputExtentAfterOffset.isPositive()) {
splitNodes.push(new Leaf(inputExtentAfterOffset, outputExtentAfterOffset, null));
}
inputOffset = this.inputExtent;
outputOffset = this.outputExtent;
}
return {
splitNodes: splitNodes,
inputOffset: inputOffset,
outputOffset: outputOffset
};
};
Leaf.prototype.merge = function(rightNeighbor) {
var ref, ref1, result;
if (((this.content != null) === (rightNeighbor.content != null)) || isEmpty(this) || isEmpty(rightNeighbor)) {
this.outputExtent = this.outputExtent.traverse(rightNeighbor.outputExtent);
this.inputExtent = this.inputExtent.traverse(rightNeighbor.inputExtent);
this.content = ((ref = this.content) != null ? ref : "") + ((ref1 = rightNeighbor.content) != null ? ref1 : "");
if (this.content === "" && this.outputExtent.isPositive()) {
this.content = null;
}
result = {
inputExtent: rightNeighbor.inputExtent,
outputExtent: rightNeighbor.outputExtent
};
rightNeighbor.inputExtent = rightNeighbor.outputExtent = Point.ZERO;
rightNeighbor.content = null;
return result;
}
};
Leaf.prototype.toString = function(indentLevel) {
var i, indent, j, ref;
if (indentLevel == null) {
indentLevel = 0;
}
indent = "";
for (i = j = 0, ref = indentLevel; j < ref; i = j += 1) {
indent += " ";
}
if (this.content != null) {
return indent + "[Leaf " + this.inputExtent + " " + this.outputExtent + " " + (JSON.stringify(this.content)) + "]";
} else {
return indent + "[Leaf " + this.inputExtent + " " + this.outputExtent + "]";
}
};
return Leaf;
})();
RegionIterator = (function() {
function RegionIterator(patch, path) {
this.patch = patch;
this.path = path;
if (this.path == null) {
this.path = [];
this.descendToLeftmostLeaf(this.patch.rootNode);
}
}
RegionIterator.prototype.next = function() {
var entry, nextChild, parentEntry, ref, ref1, value;
while ((entry = last(this.path)) && entry.inputOffset.isEqual(entry.node.inputExtent) && entry.outputOffset.isEqual(entry.node.outputExtent)) {
this.path.pop();
if (parentEntry = last(this.path)) {
parentEntry.childIndex++;
parentEntry.inputOffset = parentEntry.inputOffset.traverse(entry.inputOffset);
parentEntry.outputOffset = parentEntry.outputOffset.traverse(entry.outputOffset);
if (nextChild = parentEntry.node.children[parentEntry.childIndex]) {
this.descendToLeftmostLeaf(nextChild);
entry = last(this.path);
}
} else {
this.path.push(entry);
return {
value: null,
done: true
};
}
}
value = (ref = (ref1 = entry.node.content) != null ? ref1.slice(entry.outputOffset.column) : void 0) != null ? ref : null;
entry.outputOffset = entry.node.outputExtent;
entry.inputOffset = entry.node.inputExtent;
return {
value: value,
done: false
};
};
RegionIterator.prototype.seek = function(targetOutputOffset) {
var child, childIndex, childInputEnd, childInputStart, childOutputEnd, childOutputStart, inputOffset, j, len, node, outputOffset, ref;
this.path.length = 0;
node = this.patch.rootNode;
while (true) {
if (node.children != null) {
childInputEnd = Point.ZERO;
childOutputEnd = Point.ZERO;
ref = node.children;
for (childIndex = j = 0, len = ref.length; j < len; childIndex = ++j) {
child = ref[childIndex];
childInputStart = childInputEnd;
childOutputStart = childOutputEnd;
childInputEnd = childInputStart.traverse(child.inputExtent);
childOutputEnd = childOutputStart.traverse(child.outputExtent);
if (childOutputEnd.compare(targetOutputOffset) >= 0) {
inputOffset = childInputStart;
outputOffset = childOutputStart;
this.path.push({
node: node,
childIndex: childIndex,
inputOffset: inputOffset,
outputOffset: outputOffset
});
targetOutputOffset = targetOutputOffset.traversalFrom(childOutputStart);
node = child;
break;
}
}
} else {
if (targetOutputOffset.isEqual(node.outputExtent)) {
inputOffset = node.inputExtent;
} else {
inputOffset = Point.min(node.inputExtent, targetOutputOffset);
}
outputOffset = targetOutputOffset;
childIndex = null;
this.path.push({
node: node,
inputOffset: inputOffset,
outputOffset: outputOffset,
childIndex: childIndex
});
break;
}
}
return this;
};
RegionIterator.prototype.seekToInputPosition = function(targetInputOffset) {
var child, childIndex, childInputEnd, childInputStart, childOutputEnd, childOutputStart, inputOffset, j, len, node, outputOffset, ref;
this.path.length = 0;
node = this.patch.rootNode;
while (true) {
if (node.children != null) {
childInputEnd = Point.ZERO;
childOutputEnd = Point.ZERO;
ref = node.children;
for (childIndex = j = 0, len = ref.length; j < len; childIndex = ++j) {
child = ref[childIndex];
childInputStart = childInputEnd;
childOutputStart = childOutputEnd;
childInputEnd = childInputStart.traverse(child.inputExtent);
childOutputEnd = childOutputStart.traverse(child.outputExtent);
if (childInputEnd.compare(targetInputOffset) >= 0) {
inputOffset = childInputStart;
outputOffset = childOutputStart;
this.path.push({
node: node,
childIndex: childIndex,
inputOffset: inputOffset,
outputOffset: outputOffset
});
targetInputOffset = targetInputOffset.traversalFrom(childInputStart);
node = child;
break;
}
}
} else {
inputOffset = targetInputOffset;
if (targetInputOffset.isEqual(node.inputExtent)) {
outputOffset = node.outputExtent;
} else {
outputOffset = Point.min(node.outputExtent, targetInputOffset);
}
childIndex = null;
this.path.push({
node: node,
inputOffset: inputOffset,
outputOffset: outputOffset,
childIndex: childIndex
});
break;
}
}
return this;
};
RegionIterator.prototype.splice = function(oldOutputExtent, newExtent, newContent) {
var inputExtent, rightEdge;
rightEdge = this.copy().seek(this.getOutputPosition().traverse(oldOutputExtent));
inputExtent = rightEdge.getInputPosition().traversalFrom(this.getInputPosition());
this.deleteUntil(rightEdge);
return this.insert(inputExtent, newExtent, newContent);
};
RegionIterator.prototype.getOutputPosition = function() {
var entry, j, len, ref, result;
result = Point.ZERO;
ref = this.path;
for (j = 0, len = ref.length; j < len; j++) {
entry = ref[j];
result = result.traverse(entry.outputOffset);
}
return result;
};
RegionIterator.prototype.getInputPosition = function() {
var inputOffset, j, len, node, outputOffset, ref, ref1, result;
result = Point.ZERO;
ref = this.path;
for (j = 0, len = ref.length; j < len; j++) {
ref1 = ref[j], node = ref1.node, inputOffset = ref1.inputOffset, outputOffset = ref1.outputOffset;
result = result.traverse(inputOffset);
}
return result;
};
RegionIterator.prototype.copy = function() {
return new RegionIterator(this.patch, this.path.slice());
};
RegionIterator.prototype.descendToLeftmostLeaf = function(node) {
var entry, results;
results = [];
while (true) {
entry = {
node: node,
outputOffset: Point.ZERO,
inputOffset: Point.ZERO,
childIndex: null
};
this.path.push(entry);
if (node.children != null) {
entry.childIndex = 0;
results.push(node = node.children[0]);
} else {
break;
}
}
return results;
};
RegionIterator.prototype.deleteUntil = function(rightIterator) {
var childIndex, i, inputOffset, j, k, left, meetingIndex, node, outputOffset, ref, ref1, ref2, ref3, right, spliceIndex, totalInputOffset, totalOutputOffset;
meetingIndex = null;
totalInputOffset = Point.ZERO;
totalOutputOffset = Point.ZERO;
ref = this.path;
for (i = j = ref.length - 1; j >= 0; i = j += -1) {
ref1 = ref[i], node = ref1.node, inputOffset = ref1.inputOffset, outputOffset = ref1.outputOffset, childIndex = ref1.childIndex;
if (node === rightIterator.path[i].node) {
meetingIndex = i;
break;
}
if (node.content != null) {
node.content = node.content.slice(0, outputOffset.column);
} else if (node.children != null) {
node.children.splice(childIndex + 1);
}
totalInputOffset = inputOffset.traverse(totalInputOffset);
totalOutputOffset = outputOffset.traverse(totalOutputOffset);
node.inputExtent = totalInputOffset;
node.outputExtent = totalOutputOffset;
}
totalInputOffset = Point.ZERO;
totalOutputOffset = Point.ZERO;
ref2 = rightIterator.path;
for (i = k = ref2.length - 1; k >= 0; i = k += -1) {
ref3 = ref2[i], node = ref3.node, inputOffset = ref3.inputOffset, outputOffset = ref3.outputOffset, childIndex = ref3.childIndex;
if (i === meetingIndex) {
break;
}
if (node.content != null) {
node.content = node.content.slice(outputOffset.column);
} else if (node.children != null) {
if (isEmpty(node.children[childIndex])) {
node.children.splice(childIndex, 1);
}
node.children.splice(0, childIndex);
}
totalInputOffset = inputOffset.traverse(totalInputOffset);
totalOutputOffset = outputOffset.traverse(totalOutputOffset);
node.inputExtent = node.inputExtent.traversalFrom(totalInputOffset);
node.outputExtent = node.outputExtent.traversalFrom(totalOutputOffset);
}
left = this.path[meetingIndex];
right = rightIterator.path[meetingIndex];
node = left.node;
node.outputExtent = left.outputOffset.traverse(node.outputExtent.traversalFrom(right.outputOffset));
node.inputExtent = left.inputOffset.traverse(node.inputExtent.traversalFrom(right.inputOffset));
if (node.content != null) {
node.content = node.content.slice(0, left.outputOffset.column) + node.content.slice(right.outputOffset.column);
} else if (node.children != null) {
spliceIndex = left.childIndex + 1;
if (isEmpty(node.children[right.childIndex])) {
node.children.splice(right.childIndex, 1);
}
node.children.splice(spliceIndex, right.childIndex - spliceIndex);
}
return this;
};
RegionIterator.prototype.insert = function(newInputExtent, newOutputExtent, newContent) {
var childIndex, entry, inputOffset, j, newPath, node, outputOffset, ref, ref1, ref2, ref3, ref4, ref5, splitNodes;
newPath = [];
splitNodes = null;
ref = this.path;
for (j = ref.length - 1; j >= 0; j += -1) {
ref1 = ref[j], node = ref1.node, inputOffset = ref1.inputOffset, outputOffset = ref1.outputOffset, childIndex = ref1.childIndex;
if (node instanceof Leaf) {
ref2 = node.insert(inputOffset, outputOffset, newInputExtent, newOutputExtent, newContent), splitNodes = ref2.splitNodes, inputOffset = ref2.inputOffset, outputOffset = ref2.outputOffset;
} else {
ref3 = node.splice(childIndex, splitNodes), splitNodes = ref3.splitNodes, inputOffset = ref3.inputOffset, outputOffset = ref3.outputOffset, childIndex = ref3.childIndex;
}
newPath.unshift({
node: node,
inputOffset: inputOffset,
outputOffset: outputOffset,
childIndex: childIndex
});
}
if (splitNodes != null) {
node = this.patch.rootNode = new Node([node]);
ref4 = node.splice(0, splitNodes), inputOffset = ref4.inputOffset, outputOffset = ref4.outputOffset, childIndex = ref4.childIndex;
newPath.unshift({
node: node,
inputOffset: inputOffset,
outputOffset: outputOffset,
childIndex: childIndex
});
}
while (((ref5 = this.patch.rootNode.children) != null ? ref5.length : void 0) === 1) {
this.patch.rootNode = this.patch.rootNode.children[0];
newPath.shift();
}
entry = last(newPath);
if (entry.outputOffset.isEqual(entry.node.outputExtent)) {
entry.inputOffset = entry.node.inputExtent;
} else {
entry.inputOffset = Point.min(entry.node.inputExtent, entry.outputOffset);
}
this.path = newPath;
return this;
};
RegionIterator.prototype.toString = function() {
var childIndex, entries, inputOffset, node, outputOffset;
entries = (function() {
var j, len, ref, ref1, results;
ref = this.path;
results = [];
for (j = 0, len = ref.length; j < len; j++) {
ref1 = ref[j], node = ref1.node, inputOffset = ref1.inputOffset, outputOffset = ref1.outputOffset, childIndex = ref1.childIndex;
results.push(" {inputOffset:" + inputOffset + ", outputOffset:" + outputOffset + ", childIndex:" + childIndex + "}");
}
return results;
}).call(this);
return "[RegionIterator\n" + (entries.join("\n")) + "]";
};
return RegionIterator;
})();
ChangeIterator = (function() {
function ChangeIterator(patchIterator) {
this.patchIterator = patchIterator;
this.inputPosition = Point.ZERO;
this.outputPosition = Point.ZERO;
}
ChangeIterator.prototype.next = function() {
var content, lastInputPosition, lastOutputPosition, newExtent, next, oldExtent, position;
while (!(next = this.patchIterator.next()).done) {
lastInputPosition = this.inputPosition;
lastOutputPosition = this.outputPosition;
this.inputPosition = this.patchIterator.getInputPosition();
this.outputPosition = this.patchIterator.getOutputPosition();
if ((content = next.value) != null) {
position = lastOutputPosition;
oldExtent = this.inputPosition.traversalFrom(lastInputPosition);
newExtent = this.outputPosition.traversalFrom(lastOutputPosition);
return {
done: false,
value: {
position: position,
oldExtent: oldExtent,
newExtent: newExtent,
content: content
}
};
}
}
return {
done: true,
value: null
};
};
return ChangeIterator;
})();
module.exports = Patch = (function() {
function Patch() {
this.clear();
}
Patch.prototype.splice = function(spliceOutputStart, oldOutputExtent, newOutputExtent, content) {
var iterator;
iterator = this.regions();
iterator.seek(spliceOutputStart);
return iterator.splice(oldOutputExtent, newOutputExtent, content);
};
Patch.prototype.clear = function() {
return this.rootNode = new Leaf(Point.INFINITY, Point.INFINITY, null);
};
Patch.prototype.regions = function() {
return new RegionIterator(this);
};
Patch.prototype.changes = function() {
return new ChangeIterator(this.regions());
};
Patch.prototype.toInputPosition = function(outputPosition) {
return this.regions().seek(outputPosition).getInputPosition();
};
Patch.prototype.toOutputPosition = function(inputPosition) {
return this.regions().seekToInputPosition(inputPosition).getOutputPosition();
};
return Patch;
})();
}).call(this);