lake-codemirror
Version:
CodeMirror for Lake
1,345 lines (1,328 loc) • 1.61 MB
JavaScript
// These are filled with ranges (rangeFrom[i] up to but not including
// rangeTo[i]) of code points that count as extending characters.
let rangeFrom = [], rangeTo = []
;(() => {
// Compressed representation of the Grapheme_Cluster_Break=Extend
// information from
// http://www.unicode.org/Public/16.0.0/ucd/auxiliary/GraphemeBreakProperty.txt.
// Each pair of elements represents a range, as an offet from the
// previous range and a length. Numbers are in base-36, with the empty
// string being a shorthand for 1.
let numbers = "lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map(s => s ? parseInt(s, 36) : 1);
for (let i = 0, n = 0; i < numbers.length; i++)
(i % 2 ? rangeTo : rangeFrom).push(n = n + numbers[i]);
})();
function isExtendingChar(code) {
if (code < 768) return false
for (let from = 0, to = rangeFrom.length;;) {
let mid = (from + to) >> 1;
if (code < rangeFrom[mid]) to = mid;
else if (code >= rangeTo[mid]) from = mid + 1;
else return true
if (from == to) return false
}
}
function isRegionalIndicator(code) {
return code >= 0x1F1E6 && code <= 0x1F1FF
}
const ZWJ = 0x200d;
function findClusterBreak$1(str, pos, forward = true, includeExtending = true) {
return (forward ? nextClusterBreak : prevClusterBreak)(str, pos, includeExtending)
}
function nextClusterBreak(str, pos, includeExtending) {
if (pos == str.length) return pos
// If pos is in the middle of a surrogate pair, move to its start
if (pos && surrogateLow$1(str.charCodeAt(pos)) && surrogateHigh$1(str.charCodeAt(pos - 1))) pos--;
let prev = codePointAt$1(str, pos);
pos += codePointSize$1(prev);
while (pos < str.length) {
let next = codePointAt$1(str, pos);
if (prev == ZWJ || next == ZWJ || includeExtending && isExtendingChar(next)) {
pos += codePointSize$1(next);
prev = next;
} else if (isRegionalIndicator(next)) {
let countBefore = 0, i = pos - 2;
while (i >= 0 && isRegionalIndicator(codePointAt$1(str, i))) { countBefore++; i -= 2; }
if (countBefore % 2 == 0) break
else pos += 2;
} else {
break
}
}
return pos
}
function prevClusterBreak(str, pos, includeExtending) {
while (pos > 0) {
let found = nextClusterBreak(str, pos - 2, includeExtending);
if (found < pos) return found
pos--;
}
return 0
}
function codePointAt$1(str, pos) {
let code0 = str.charCodeAt(pos);
if (!surrogateHigh$1(code0) || pos + 1 == str.length) return code0
let code1 = str.charCodeAt(pos + 1);
if (!surrogateLow$1(code1)) return code0
return ((code0 - 0xd800) << 10) + (code1 - 0xdc00) + 0x10000
}
function surrogateLow$1(ch) { return ch >= 0xDC00 && ch < 0xE000 }
function surrogateHigh$1(ch) { return ch >= 0xD800 && ch < 0xDC00 }
function codePointSize$1(code) { return code < 0x10000 ? 1 : 2 }
/**
The data structure for documents. @nonabstract
*/
class Text {
/**
Get the line description around the given position.
*/
lineAt(pos) {
if (pos < 0 || pos > this.length)
throw new RangeError(`Invalid position ${pos} in document of length ${this.length}`);
return this.lineInner(pos, false, 1, 0);
}
/**
Get the description for the given (1-based) line number.
*/
line(n) {
if (n < 1 || n > this.lines)
throw new RangeError(`Invalid line number ${n} in ${this.lines}-line document`);
return this.lineInner(n, true, 1, 0);
}
/**
Replace a range of the text with the given content.
*/
replace(from, to, text) {
[from, to] = clip(this, from, to);
let parts = [];
this.decompose(0, from, parts, 2 /* Open.To */);
if (text.length)
text.decompose(0, text.length, parts, 1 /* Open.From */ | 2 /* Open.To */);
this.decompose(to, this.length, parts, 1 /* Open.From */);
return TextNode.from(parts, this.length - (to - from) + text.length);
}
/**
Append another document to this one.
*/
append(other) {
return this.replace(this.length, this.length, other);
}
/**
Retrieve the text between the given points.
*/
slice(from, to = this.length) {
[from, to] = clip(this, from, to);
let parts = [];
this.decompose(from, to, parts, 0);
return TextNode.from(parts, to - from);
}
/**
Test whether this text is equal to another instance.
*/
eq(other) {
if (other == this)
return true;
if (other.length != this.length || other.lines != this.lines)
return false;
let start = this.scanIdentical(other, 1), end = this.length - this.scanIdentical(other, -1);
let a = new RawTextCursor(this), b = new RawTextCursor(other);
for (let skip = start, pos = start;;) {
a.next(skip);
b.next(skip);
skip = 0;
if (a.lineBreak != b.lineBreak || a.done != b.done || a.value != b.value)
return false;
pos += a.value.length;
if (a.done || pos >= end)
return true;
}
}
/**
Iterate over the text. When `dir` is `-1`, iteration happens
from end to start. This will return lines and the breaks between
them as separate strings.
*/
iter(dir = 1) { return new RawTextCursor(this, dir); }
/**
Iterate over a range of the text. When `from` > `to`, the
iterator will run in reverse.
*/
iterRange(from, to = this.length) { return new PartialTextCursor(this, from, to); }
/**
Return a cursor that iterates over the given range of lines,
_without_ returning the line breaks between, and yielding empty
strings for empty lines.
When `from` and `to` are given, they should be 1-based line numbers.
*/
iterLines(from, to) {
let inner;
if (from == null) {
inner = this.iter();
}
else {
if (to == null)
to = this.lines + 1;
let start = this.line(from).from;
inner = this.iterRange(start, Math.max(start, to == this.lines + 1 ? this.length : to <= 1 ? 0 : this.line(to - 1).to));
}
return new LineCursor(inner);
}
/**
Return the document as a string, using newline characters to
separate lines.
*/
toString() { return this.sliceString(0); }
/**
Convert the document to an array of lines (which can be
deserialized again via [`Text.of`](https://codemirror.net/6/docs/ref/#state.Text^of)).
*/
toJSON() {
let lines = [];
this.flatten(lines);
return lines;
}
/**
@internal
*/
constructor() { }
/**
Create a `Text` instance for the given array of lines.
*/
static of(text) {
if (text.length == 0)
throw new RangeError("A document must have at least one line");
if (text.length == 1 && !text[0])
return Text.empty;
return text.length <= 32 /* Tree.Branch */ ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, []));
}
}
// Leaves store an array of line strings. There are always line breaks
// between these strings. Leaves are limited in size and have to be
// contained in TextNode instances for bigger documents.
class TextLeaf extends Text {
constructor(text, length = textLength(text)) {
super();
this.text = text;
this.length = length;
}
get lines() { return this.text.length; }
get children() { return null; }
lineInner(target, isLine, line, offset) {
for (let i = 0;; i++) {
let string = this.text[i], end = offset + string.length;
if ((isLine ? line : end) >= target)
return new Line$1(offset, end, line, string);
offset = end + 1;
line++;
}
}
decompose(from, to, target, open) {
let text = from <= 0 && to >= this.length ? this
: new TextLeaf(sliceText(this.text, from, to), Math.min(to, this.length) - Math.max(0, from));
if (open & 1 /* Open.From */) {
let prev = target.pop();
let joined = appendText(text.text, prev.text.slice(), 0, text.length);
if (joined.length <= 32 /* Tree.Branch */) {
target.push(new TextLeaf(joined, prev.length + text.length));
}
else {
let mid = joined.length >> 1;
target.push(new TextLeaf(joined.slice(0, mid)), new TextLeaf(joined.slice(mid)));
}
}
else {
target.push(text);
}
}
replace(from, to, text) {
if (!(text instanceof TextLeaf))
return super.replace(from, to, text);
[from, to] = clip(this, from, to);
let lines = appendText(this.text, appendText(text.text, sliceText(this.text, 0, from)), to);
let newLen = this.length + text.length - (to - from);
if (lines.length <= 32 /* Tree.Branch */)
return new TextLeaf(lines, newLen);
return TextNode.from(TextLeaf.split(lines, []), newLen);
}
sliceString(from, to = this.length, lineSep = "\n") {
[from, to] = clip(this, from, to);
let result = "";
for (let pos = 0, i = 0; pos <= to && i < this.text.length; i++) {
let line = this.text[i], end = pos + line.length;
if (pos > from && i)
result += lineSep;
if (from < end && to > pos)
result += line.slice(Math.max(0, from - pos), to - pos);
pos = end + 1;
}
return result;
}
flatten(target) {
for (let line of this.text)
target.push(line);
}
scanIdentical() { return 0; }
static split(text, target) {
let part = [], len = -1;
for (let line of text) {
part.push(line);
len += line.length + 1;
if (part.length == 32 /* Tree.Branch */) {
target.push(new TextLeaf(part, len));
part = [];
len = -1;
}
}
if (len > -1)
target.push(new TextLeaf(part, len));
return target;
}
}
// Nodes provide the tree structure of the `Text` type. They store a
// number of other nodes or leaves, taking care to balance themselves
// on changes. There are implied line breaks _between_ the children of
// a node (but not before the first or after the last child).
class TextNode extends Text {
constructor(children, length) {
super();
this.children = children;
this.length = length;
this.lines = 0;
for (let child of children)
this.lines += child.lines;
}
lineInner(target, isLine, line, offset) {
for (let i = 0;; i++) {
let child = this.children[i], end = offset + child.length, endLine = line + child.lines - 1;
if ((isLine ? endLine : end) >= target)
return child.lineInner(target, isLine, line, offset);
offset = end + 1;
line = endLine + 1;
}
}
decompose(from, to, target, open) {
for (let i = 0, pos = 0; pos <= to && i < this.children.length; i++) {
let child = this.children[i], end = pos + child.length;
if (from <= end && to >= pos) {
let childOpen = open & ((pos <= from ? 1 /* Open.From */ : 0) | (end >= to ? 2 /* Open.To */ : 0));
if (pos >= from && end <= to && !childOpen)
target.push(child);
else
child.decompose(from - pos, to - pos, target, childOpen);
}
pos = end + 1;
}
}
replace(from, to, text) {
[from, to] = clip(this, from, to);
if (text.lines < this.lines)
for (let i = 0, pos = 0; i < this.children.length; i++) {
let child = this.children[i], end = pos + child.length;
// Fast path: if the change only affects one child and the
// child's size remains in the acceptable range, only update
// that child
if (from >= pos && to <= end) {
let updated = child.replace(from - pos, to - pos, text);
let totalLines = this.lines - child.lines + updated.lines;
if (updated.lines < (totalLines >> (5 /* Tree.BranchShift */ - 1)) &&
updated.lines > (totalLines >> (5 /* Tree.BranchShift */ + 1))) {
let copy = this.children.slice();
copy[i] = updated;
return new TextNode(copy, this.length - (to - from) + text.length);
}
return super.replace(pos, end, updated);
}
pos = end + 1;
}
return super.replace(from, to, text);
}
sliceString(from, to = this.length, lineSep = "\n") {
[from, to] = clip(this, from, to);
let result = "";
for (let i = 0, pos = 0; i < this.children.length && pos <= to; i++) {
let child = this.children[i], end = pos + child.length;
if (pos > from && i)
result += lineSep;
if (from < end && to > pos)
result += child.sliceString(from - pos, to - pos, lineSep);
pos = end + 1;
}
return result;
}
flatten(target) {
for (let child of this.children)
child.flatten(target);
}
scanIdentical(other, dir) {
if (!(other instanceof TextNode))
return 0;
let length = 0;
let [iA, iB, eA, eB] = dir > 0 ? [0, 0, this.children.length, other.children.length]
: [this.children.length - 1, other.children.length - 1, -1, -1];
for (;; iA += dir, iB += dir) {
if (iA == eA || iB == eB)
return length;
let chA = this.children[iA], chB = other.children[iB];
if (chA != chB)
return length + chA.scanIdentical(chB, dir);
length += chA.length + 1;
}
}
static from(children, length = children.reduce((l, ch) => l + ch.length + 1, -1)) {
let lines = 0;
for (let ch of children)
lines += ch.lines;
if (lines < 32 /* Tree.Branch */) {
let flat = [];
for (let ch of children)
ch.flatten(flat);
return new TextLeaf(flat, length);
}
let chunk = Math.max(32 /* Tree.Branch */, lines >> 5 /* Tree.BranchShift */), maxChunk = chunk << 1, minChunk = chunk >> 1;
let chunked = [], currentLines = 0, currentLen = -1, currentChunk = [];
function add(child) {
let last;
if (child.lines > maxChunk && child instanceof TextNode) {
for (let node of child.children)
add(node);
}
else if (child.lines > minChunk && (currentLines > minChunk || !currentLines)) {
flush();
chunked.push(child);
}
else if (child instanceof TextLeaf && currentLines &&
(last = currentChunk[currentChunk.length - 1]) instanceof TextLeaf &&
child.lines + last.lines <= 32 /* Tree.Branch */) {
currentLines += child.lines;
currentLen += child.length + 1;
currentChunk[currentChunk.length - 1] = new TextLeaf(last.text.concat(child.text), last.length + 1 + child.length);
}
else {
if (currentLines + child.lines > chunk)
flush();
currentLines += child.lines;
currentLen += child.length + 1;
currentChunk.push(child);
}
}
function flush() {
if (currentLines == 0)
return;
chunked.push(currentChunk.length == 1 ? currentChunk[0] : TextNode.from(currentChunk, currentLen));
currentLen = -1;
currentLines = currentChunk.length = 0;
}
for (let child of children)
add(child);
flush();
return chunked.length == 1 ? chunked[0] : new TextNode(chunked, length);
}
}
Text.empty = /*@__PURE__*/new TextLeaf([""], 0);
function textLength(text) {
let length = -1;
for (let line of text)
length += line.length + 1;
return length;
}
function appendText(text, target, from = 0, to = 1e9) {
for (let pos = 0, i = 0, first = true; i < text.length && pos <= to; i++) {
let line = text[i], end = pos + line.length;
if (end >= from) {
if (end > to)
line = line.slice(0, to - pos);
if (pos < from)
line = line.slice(from - pos);
if (first) {
target[target.length - 1] += line;
first = false;
}
else
target.push(line);
}
pos = end + 1;
}
return target;
}
function sliceText(text, from, to) {
return appendText(text, [""], from, to);
}
class RawTextCursor {
constructor(text, dir = 1) {
this.dir = dir;
this.done = false;
this.lineBreak = false;
this.value = "";
this.nodes = [text];
this.offsets = [dir > 0 ? 1 : (text instanceof TextLeaf ? text.text.length : text.children.length) << 1];
}
nextInner(skip, dir) {
this.done = this.lineBreak = false;
for (;;) {
let last = this.nodes.length - 1;
let top = this.nodes[last], offsetValue = this.offsets[last], offset = offsetValue >> 1;
let size = top instanceof TextLeaf ? top.text.length : top.children.length;
if (offset == (dir > 0 ? size : 0)) {
if (last == 0) {
this.done = true;
this.value = "";
return this;
}
if (dir > 0)
this.offsets[last - 1]++;
this.nodes.pop();
this.offsets.pop();
}
else if ((offsetValue & 1) == (dir > 0 ? 0 : 1)) {
this.offsets[last] += dir;
if (skip == 0) {
this.lineBreak = true;
this.value = "\n";
return this;
}
skip--;
}
else if (top instanceof TextLeaf) {
// Move to the next string
let next = top.text[offset + (dir < 0 ? -1 : 0)];
this.offsets[last] += dir;
if (next.length > Math.max(0, skip)) {
this.value = skip == 0 ? next : dir > 0 ? next.slice(skip) : next.slice(0, next.length - skip);
return this;
}
skip -= next.length;
}
else {
let next = top.children[offset + (dir < 0 ? -1 : 0)];
if (skip > next.length) {
skip -= next.length;
this.offsets[last] += dir;
}
else {
if (dir < 0)
this.offsets[last]--;
this.nodes.push(next);
this.offsets.push(dir > 0 ? 1 : (next instanceof TextLeaf ? next.text.length : next.children.length) << 1);
}
}
}
}
next(skip = 0) {
if (skip < 0) {
this.nextInner(-skip, (-this.dir));
skip = this.value.length;
}
return this.nextInner(skip, this.dir);
}
}
class PartialTextCursor {
constructor(text, start, end) {
this.value = "";
this.done = false;
this.cursor = new RawTextCursor(text, start > end ? -1 : 1);
this.pos = start > end ? text.length : 0;
this.from = Math.min(start, end);
this.to = Math.max(start, end);
}
nextInner(skip, dir) {
if (dir < 0 ? this.pos <= this.from : this.pos >= this.to) {
this.value = "";
this.done = true;
return this;
}
skip += Math.max(0, dir < 0 ? this.pos - this.to : this.from - this.pos);
let limit = dir < 0 ? this.pos - this.from : this.to - this.pos;
if (skip > limit)
skip = limit;
limit -= skip;
let { value } = this.cursor.next(skip);
this.pos += (value.length + skip) * dir;
this.value = value.length <= limit ? value : dir < 0 ? value.slice(value.length - limit) : value.slice(0, limit);
this.done = !this.value;
return this;
}
next(skip = 0) {
if (skip < 0)
skip = Math.max(skip, this.from - this.pos);
else if (skip > 0)
skip = Math.min(skip, this.to - this.pos);
return this.nextInner(skip, this.cursor.dir);
}
get lineBreak() { return this.cursor.lineBreak && this.value != ""; }
}
class LineCursor {
constructor(inner) {
this.inner = inner;
this.afterBreak = true;
this.value = "";
this.done = false;
}
next(skip = 0) {
let { done, lineBreak, value } = this.inner.next(skip);
if (done && this.afterBreak) {
this.value = "";
this.afterBreak = false;
}
else if (done) {
this.done = true;
this.value = "";
}
else if (lineBreak) {
if (this.afterBreak) {
this.value = "";
}
else {
this.afterBreak = true;
this.next();
}
}
else {
this.value = value;
this.afterBreak = false;
}
return this;
}
get lineBreak() { return false; }
}
if (typeof Symbol != "undefined") {
Text.prototype[Symbol.iterator] = function () { return this.iter(); };
RawTextCursor.prototype[Symbol.iterator] = PartialTextCursor.prototype[Symbol.iterator] =
LineCursor.prototype[Symbol.iterator] = function () { return this; };
}
/**
This type describes a line in the document. It is created
on-demand when lines are [queried](https://codemirror.net/6/docs/ref/#state.Text.lineAt).
*/
let Line$1 = class Line {
/**
@internal
*/
constructor(
/**
The position of the start of the line.
*/
from,
/**
The position at the end of the line (_before_ the line break,
or at the end of document for the last line).
*/
to,
/**
This line's line number (1-based).
*/
number,
/**
The line's content.
*/
text) {
this.from = from;
this.to = to;
this.number = number;
this.text = text;
}
/**
The length of the line (not including any line break after it).
*/
get length() { return this.to - this.from; }
};
function clip(text, from, to) {
from = Math.max(0, Math.min(text.length, from));
return [from, Math.max(from, Math.min(text.length, to))];
}
/**
Returns a next grapheme cluster break _after_ (not equal to)
`pos`, if `forward` is true, or before otherwise. Returns `pos`
itself if no further cluster break is available in the string.
Moves across surrogate pairs, extending characters (when
`includeExtending` is true), characters joined with zero-width
joiners, and flag emoji.
*/
function findClusterBreak(str, pos, forward = true, includeExtending = true) {
return findClusterBreak$1(str, pos, forward, includeExtending);
}
function surrogateLow(ch) { return ch >= 0xDC00 && ch < 0xE000; }
function surrogateHigh(ch) { return ch >= 0xD800 && ch < 0xDC00; }
/**
Find the code point at the given position in a string (like the
[`codePointAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)
string method).
*/
function codePointAt(str, pos) {
let code0 = str.charCodeAt(pos);
if (!surrogateHigh(code0) || pos + 1 == str.length)
return code0;
let code1 = str.charCodeAt(pos + 1);
if (!surrogateLow(code1))
return code0;
return ((code0 - 0xd800) << 10) + (code1 - 0xdc00) + 0x10000;
}
/**
The amount of positions a character takes up in a JavaScript string.
*/
function codePointSize(code) { return code < 0x10000 ? 1 : 2; }
const DefaultSplit = /\r\n?|\n/;
/**
Distinguishes different ways in which positions can be mapped.
*/
var MapMode = /*@__PURE__*/(function (MapMode) {
/**
Map a position to a valid new position, even when its context
was deleted.
*/
MapMode[MapMode["Simple"] = 0] = "Simple";
/**
Return null if deletion happens across the position.
*/
MapMode[MapMode["TrackDel"] = 1] = "TrackDel";
/**
Return null if the character _before_ the position is deleted.
*/
MapMode[MapMode["TrackBefore"] = 2] = "TrackBefore";
/**
Return null if the character _after_ the position is deleted.
*/
MapMode[MapMode["TrackAfter"] = 3] = "TrackAfter";
return MapMode})(MapMode || (MapMode = {}));
/**
A change description is a variant of [change set](https://codemirror.net/6/docs/ref/#state.ChangeSet)
that doesn't store the inserted text. As such, it can't be
applied, but is cheaper to store and manipulate.
*/
class ChangeDesc {
// Sections are encoded as pairs of integers. The first is the
// length in the current document, and the second is -1 for
// unaffected sections, and the length of the replacement content
// otherwise. So an insertion would be (0, n>0), a deletion (n>0,
// 0), and a replacement two positive numbers.
/**
@internal
*/
constructor(
/**
@internal
*/
sections) {
this.sections = sections;
}
/**
The length of the document before the change.
*/
get length() {
let result = 0;
for (let i = 0; i < this.sections.length; i += 2)
result += this.sections[i];
return result;
}
/**
The length of the document after the change.
*/
get newLength() {
let result = 0;
for (let i = 0; i < this.sections.length; i += 2) {
let ins = this.sections[i + 1];
result += ins < 0 ? this.sections[i] : ins;
}
return result;
}
/**
False when there are actual changes in this set.
*/
get empty() { return this.sections.length == 0 || this.sections.length == 2 && this.sections[1] < 0; }
/**
Iterate over the unchanged parts left by these changes. `posA`
provides the position of the range in the old document, `posB`
the new position in the changed document.
*/
iterGaps(f) {
for (let i = 0, posA = 0, posB = 0; i < this.sections.length;) {
let len = this.sections[i++], ins = this.sections[i++];
if (ins < 0) {
f(posA, posB, len);
posB += len;
}
else {
posB += ins;
}
posA += len;
}
}
/**
Iterate over the ranges changed by these changes. (See
[`ChangeSet.iterChanges`](https://codemirror.net/6/docs/ref/#state.ChangeSet.iterChanges) for a
variant that also provides you with the inserted text.)
`fromA`/`toA` provides the extent of the change in the starting
document, `fromB`/`toB` the extent of the replacement in the
changed document.
When `individual` is true, adjacent changes (which are kept
separate for [position mapping](https://codemirror.net/6/docs/ref/#state.ChangeDesc.mapPos)) are
reported separately.
*/
iterChangedRanges(f, individual = false) {
iterChanges(this, f, individual);
}
/**
Get a description of the inverted form of these changes.
*/
get invertedDesc() {
let sections = [];
for (let i = 0; i < this.sections.length;) {
let len = this.sections[i++], ins = this.sections[i++];
if (ins < 0)
sections.push(len, ins);
else
sections.push(ins, len);
}
return new ChangeDesc(sections);
}
/**
Compute the combined effect of applying another set of changes
after this one. The length of the document after this set should
match the length before `other`.
*/
composeDesc(other) { return this.empty ? other : other.empty ? this : composeSets(this, other); }
/**
Map this description, which should start with the same document
as `other`, over another set of changes, so that it can be
applied after it. When `before` is true, map as if the changes
in `this` happened before the ones in `other`.
*/
mapDesc(other, before = false) { return other.empty ? this : mapSet(this, other, before); }
mapPos(pos, assoc = -1, mode = MapMode.Simple) {
let posA = 0, posB = 0;
for (let i = 0; i < this.sections.length;) {
let len = this.sections[i++], ins = this.sections[i++], endA = posA + len;
if (ins < 0) {
if (endA > pos)
return posB + (pos - posA);
posB += len;
}
else {
if (mode != MapMode.Simple && endA >= pos &&
(mode == MapMode.TrackDel && posA < pos && endA > pos ||
mode == MapMode.TrackBefore && posA < pos ||
mode == MapMode.TrackAfter && endA > pos))
return null;
if (endA > pos || endA == pos && assoc < 0 && !len)
return pos == posA || assoc < 0 ? posB : posB + ins;
posB += ins;
}
posA = endA;
}
if (pos > posA)
throw new RangeError(`Position ${pos} is out of range for changeset of length ${posA}`);
return posB;
}
/**
Check whether these changes touch a given range. When one of the
changes entirely covers the range, the string `"cover"` is
returned.
*/
touchesRange(from, to = from) {
for (let i = 0, pos = 0; i < this.sections.length && pos <= to;) {
let len = this.sections[i++], ins = this.sections[i++], end = pos + len;
if (ins >= 0 && pos <= to && end >= from)
return pos < from && end > to ? "cover" : true;
pos = end;
}
return false;
}
/**
@internal
*/
toString() {
let result = "";
for (let i = 0; i < this.sections.length;) {
let len = this.sections[i++], ins = this.sections[i++];
result += (result ? " " : "") + len + (ins >= 0 ? ":" + ins : "");
}
return result;
}
/**
Serialize this change desc to a JSON-representable value.
*/
toJSON() { return this.sections; }
/**
Create a change desc from its JSON representation (as produced
by [`toJSON`](https://codemirror.net/6/docs/ref/#state.ChangeDesc.toJSON).
*/
static fromJSON(json) {
if (!Array.isArray(json) || json.length % 2 || json.some(a => typeof a != "number"))
throw new RangeError("Invalid JSON representation of ChangeDesc");
return new ChangeDesc(json);
}
/**
@internal
*/
static create(sections) { return new ChangeDesc(sections); }
}
/**
A change set represents a group of modifications to a document. It
stores the document length, and can only be applied to documents
with exactly that length.
*/
class ChangeSet extends ChangeDesc {
constructor(sections,
/**
@internal
*/
inserted) {
super(sections);
this.inserted = inserted;
}
/**
Apply the changes to a document, returning the modified
document.
*/
apply(doc) {
if (this.length != doc.length)
throw new RangeError("Applying change set to a document with the wrong length");
iterChanges(this, (fromA, toA, fromB, _toB, text) => doc = doc.replace(fromB, fromB + (toA - fromA), text), false);
return doc;
}
mapDesc(other, before = false) { return mapSet(this, other, before, true); }
/**
Given the document as it existed _before_ the changes, return a
change set that represents the inverse of this set, which could
be used to go from the document created by the changes back to
the document as it existed before the changes.
*/
invert(doc) {
let sections = this.sections.slice(), inserted = [];
for (let i = 0, pos = 0; i < sections.length; i += 2) {
let len = sections[i], ins = sections[i + 1];
if (ins >= 0) {
sections[i] = ins;
sections[i + 1] = len;
let index = i >> 1;
while (inserted.length < index)
inserted.push(Text.empty);
inserted.push(len ? doc.slice(pos, pos + len) : Text.empty);
}
pos += len;
}
return new ChangeSet(sections, inserted);
}
/**
Combine two subsequent change sets into a single set. `other`
must start in the document produced by `this`. If `this` goes
`docA` → `docB` and `other` represents `docB` → `docC`, the
returned value will represent the change `docA` → `docC`.
*/
compose(other) { return this.empty ? other : other.empty ? this : composeSets(this, other, true); }
/**
Given another change set starting in the same document, maps this
change set over the other, producing a new change set that can be
applied to the document produced by applying `other`. When
`before` is `true`, order changes as if `this` comes before
`other`, otherwise (the default) treat `other` as coming first.
Given two changes `A` and `B`, `A.compose(B.map(A))` and
`B.compose(A.map(B, true))` will produce the same document. This
provides a basic form of [operational
transformation](https://en.wikipedia.org/wiki/Operational_transformation),
and can be used for collaborative editing.
*/
map(other, before = false) { return other.empty ? this : mapSet(this, other, before, true); }
/**
Iterate over the changed ranges in the document, calling `f` for
each, with the range in the original document (`fromA`-`toA`)
and the range that replaces it in the new document
(`fromB`-`toB`).
When `individual` is true, adjacent changes are reported
separately.
*/
iterChanges(f, individual = false) {
iterChanges(this, f, individual);
}
/**
Get a [change description](https://codemirror.net/6/docs/ref/#state.ChangeDesc) for this change
set.
*/
get desc() { return ChangeDesc.create(this.sections); }
/**
@internal
*/
filter(ranges) {
let resultSections = [], resultInserted = [], filteredSections = [];
let iter = new SectionIter(this);
done: for (let i = 0, pos = 0;;) {
let next = i == ranges.length ? 1e9 : ranges[i++];
while (pos < next || pos == next && iter.len == 0) {
if (iter.done)
break done;
let len = Math.min(iter.len, next - pos);
addSection(filteredSections, len, -1);
let ins = iter.ins == -1 ? -1 : iter.off == 0 ? iter.ins : 0;
addSection(resultSections, len, ins);
if (ins > 0)
addInsert(resultInserted, resultSections, iter.text);
iter.forward(len);
pos += len;
}
let end = ranges[i++];
while (pos < end) {
if (iter.done)
break done;
let len = Math.min(iter.len, end - pos);
addSection(resultSections, len, -1);
addSection(filteredSections, len, iter.ins == -1 ? -1 : iter.off == 0 ? iter.ins : 0);
iter.forward(len);
pos += len;
}
}
return { changes: new ChangeSet(resultSections, resultInserted),
filtered: ChangeDesc.create(filteredSections) };
}
/**
Serialize this change set to a JSON-representable value.
*/
toJSON() {
let parts = [];
for (let i = 0; i < this.sections.length; i += 2) {
let len = this.sections[i], ins = this.sections[i + 1];
if (ins < 0)
parts.push(len);
else if (ins == 0)
parts.push([len]);
else
parts.push([len].concat(this.inserted[i >> 1].toJSON()));
}
return parts;
}
/**
Create a change set for the given changes, for a document of the
given length, using `lineSep` as line separator.
*/
static of(changes, length, lineSep) {
let sections = [], inserted = [], pos = 0;
let total = null;
function flush(force = false) {
if (!force && !sections.length)
return;
if (pos < length)
addSection(sections, length - pos, -1);
let set = new ChangeSet(sections, inserted);
total = total ? total.compose(set.map(total)) : set;
sections = [];
inserted = [];
pos = 0;
}
function process(spec) {
if (Array.isArray(spec)) {
for (let sub of spec)
process(sub);
}
else if (spec instanceof ChangeSet) {
if (spec.length != length)
throw new RangeError(`Mismatched change set length (got ${spec.length}, expected ${length})`);
flush();
total = total ? total.compose(spec.map(total)) : spec;
}
else {
let { from, to = from, insert } = spec;
if (from > to || from < 0 || to > length)
throw new RangeError(`Invalid change range ${from} to ${to} (in doc of length ${length})`);
let insText = !insert ? Text.empty : typeof insert == "string" ? Text.of(insert.split(lineSep || DefaultSplit)) : insert;
let insLen = insText.length;
if (from == to && insLen == 0)
return;
if (from < pos)
flush();
if (from > pos)
addSection(sections, from - pos, -1);
addSection(sections, to - from, insLen);
addInsert(inserted, sections, insText);
pos = to;
}
}
process(changes);
flush(!total);
return total;
}
/**
Create an empty changeset of the given length.
*/
static empty(length) {
return new ChangeSet(length ? [length, -1] : [], []);
}
/**
Create a changeset from its JSON representation (as produced by
[`toJSON`](https://codemirror.net/6/docs/ref/#state.ChangeSet.toJSON).
*/
static fromJSON(json) {
if (!Array.isArray(json))
throw new RangeError("Invalid JSON representation of ChangeSet");
let sections = [], inserted = [];
for (let i = 0; i < json.length; i++) {
let part = json[i];
if (typeof part == "number") {
sections.push(part, -1);
}
else if (!Array.isArray(part) || typeof part[0] != "number" || part.some((e, i) => i && typeof e != "string")) {
throw new RangeError("Invalid JSON representation of ChangeSet");
}
else if (part.length == 1) {
sections.push(part[0], 0);
}
else {
while (inserted.length < i)
inserted.push(Text.empty);
inserted[i] = Text.of(part.slice(1));
sections.push(part[0], inserted[i].length);
}
}
return new ChangeSet(sections, inserted);
}
/**
@internal
*/
static createSet(sections, inserted) {
return new ChangeSet(sections, inserted);
}
}
function addSection(sections, len, ins, forceJoin = false) {
if (len == 0 && ins <= 0)
return;
let last = sections.length - 2;
if (last >= 0 && ins <= 0 && ins == sections[last + 1])
sections[last] += len;
else if (last >= 0 && len == 0 && sections[last] == 0)
sections[last + 1] += ins;
else if (forceJoin) {
sections[last] += len;
sections[last + 1] += ins;
}
else
sections.push(len, ins);
}
function addInsert(values, sections, value) {
if (value.length == 0)
return;
let index = (sections.length - 2) >> 1;
if (index < values.length) {
values[values.length - 1] = values[values.length - 1].append(value);
}
else {
while (values.length < index)
values.push(Text.empty);
values.push(value);
}
}
function iterChanges(desc, f, individual) {
let inserted = desc.inserted;
for (let posA = 0, posB = 0, i = 0; i < desc.sections.length;) {
let len = desc.sections[i++], ins = desc.sections[i++];
if (ins < 0) {
posA += len;
posB += len;
}
else {
let endA = posA, endB = posB, text = Text.empty;
for (;;) {
endA += len;
endB += ins;
if (ins && inserted)
text = text.append(inserted[(i - 2) >> 1]);
if (individual || i == desc.sections.length || desc.sections[i + 1] < 0)
break;
len = desc.sections[i++];
ins = desc.sections[i++];
}
f(posA, endA, posB, endB, text);
posA = endA;
posB = endB;
}
}
}
function mapSet(setA, setB, before, mkSet = false) {
// Produce a copy of setA that applies to the document after setB
// has been applied (assuming both start at the same document).
let sections = [], insert = mkSet ? [] : null;
let a = new SectionIter(setA), b = new SectionIter(setB);
// Iterate over both sets in parallel. inserted tracks, for changes
// in A that have to be processed piece-by-piece, whether their
// content has been inserted already, and refers to the section
// index.
for (let inserted = -1;;) {
if (a.done && b.len || b.done && a.len) {
throw new Error("Mismatched change set lengths");
}
else if (a.ins == -1 && b.ins == -1) {
// Move across ranges skipped by both sets.
let len = Math.min(a.len, b.len);
addSection(sections, len, -1);
a.forward(len);
b.forward(len);
}
else if (b.ins >= 0 && (a.ins < 0 || inserted == a.i || a.off == 0 && (b.len < a.len || b.len == a.len && !before))) {
// If there's a change in B that comes before the next change in
// A (ordered by start pos, then len, then before flag), skip
// that (and process any changes in A it covers).
let len = b.len;
addSection(sections, b.ins, -1);
while (len) {
let piece = Math.min(a.len, len);
if (a.ins >= 0 && inserted < a.i && a.len <= piece) {
addSection(sections, 0, a.ins);
if (insert)
addInsert(insert, sections, a.text);
inserted = a.i;
}
a.forward(piece);
len -= piece;
}
b.next();
}
else if (a.ins >= 0) {
// Process the part of a change in A up to the start of the next
// non-deletion change in B (if overlapping).
let len = 0, left = a.len;
while (left) {
if (b.ins == -1) {
let piece = Math.min(left, b.len);
len += piece;
left -= piece;
b.forward(piece);
}
else if (b.ins == 0 && b.len < left) {
left -= b.len;
b.next();
}
else {
break;
}
}
addSection(sections, len, inserted < a.i ? a.ins : 0);
if (insert && inserted < a.i)
addInsert(insert, sections, a.text);
inserted = a.i;
a.forward(a.len - left);
}
else if (a.done && b.done) {
return insert ? ChangeSet.createSet(sections, insert) : ChangeDesc.create(sections);
}
else {
throw new Error("Mismatched change set lengths");
}
}
}
function composeSets(setA, setB, mkSet = false) {
let sections = [];
let insert = mkSet ? [] : null;
let a = new SectionIter(setA), b = new SectionIter(setB);
for (let open = false;;) {
if (a.done && b.done) {
return insert ? ChangeSet.createSet(sections, insert) : ChangeDesc.create(sections);
}
else if (a.ins == 0) { // Deletion in A
addSection(sections, a.len, 0, open);
a.next();
}
else if (b.len == 0 && !b.done) { // Insertion in B
addSection(sections, 0, b.ins, open);
if (insert)
addInsert(insert, sections, b.text);
b.next();
}
else if (a.done || b.done) {
throw new Error("Mismatched change set lengths");
}
else {
let len = Math.min(a.len2, b.len), sectionLen = sections.length;
if (a.ins == -1) {
let insB = b.ins == -1 ? -1 : b.off ? 0 : b.ins;
addSection(sections, len, insB, open);
if (insert && insB)
addInsert(insert, sections, b.text);
}
else if (b.ins == -1) {
addSection(sections, a.off ? 0 : a.len, len, open);
if (insert)
addInsert(insert, sections, a.textBit(len));
}
else {
addSection(sections, a.off ? 0 : a.len, b.off ? 0 : b.ins, open);
if (insert && !b.off)
addInsert(insert, sections, b.text);
}
open = (a.ins > len || b.ins >= 0 && b.len > len) && (open || sections.length > sectionLen);
a.forward2(len);
b.forward(len);
}
}
}
class SectionIter {
constructor(set) {
this.set = set;
this.i = 0;
this.next();
}
next() {
let { sections } = this.set;
if (this.i < sections.length) {
this.len = sections[this.i++];
this.ins = sections[this.i++];
}
else {
this.len = 0;
this.ins = -2;
}
this.off = 0;
}
get done() { return this.ins == -2; }
get len2() { return this.ins < 0 ? this.len : this.ins; }
get text() {
let { inserted } = this.set, index = (this.i - 2) >> 1;
return index >= inserted.length ? Text.empty : inserted[index];
}
textBit(len) {
let { inserted } = this.set, index = (this.i - 2) >> 1;
return index >= inserted.length && !len ? Text.empty
: inserted[index].slice(this.off, len == null ? undefined : this.off + len);
}
forward(len) {
if (len == this.len)
this.next();
else {
this.len -= len;
this.off += len;
}
}
forward2(len) {
if (this.ins == -1)
this.forward(len);
else if (len == this.ins)
this.next();
else {
this.ins -= len;
this.off += len;
}
}
}
/**
A single selection range. When
[`allowMultipleSelections`](https://codemirror.net/6/docs/ref/#state.EditorState^allowMultipleSelections)
is enabled, a [selection](https://codemirror.net/6/docs/ref/#state.EditorSelection) may hold
multiple ranges. By default, selections hold exactly one range.
*/
class SelectionRange {
constructor(
/**
The lower boundary of the range.
*/
from,
/**
The upper boundary of the range.
*/
to, flags) {
this.from = from;
this.to = to;
this.flags = flags;
}
/**
The anchor of the range—the side that doesn't move when you
extend it.
*/
get anchor() { return this.flags & 32 /* RangeFlag.Inverted */ ? this.to : this.from; }
/**
The head of the range, which is moved when the range is
[extend