@scidian/scrimp
Version:
Customized CodeMirror editor for use with the Onsight Engine.
1,313 lines • 5.21 MB
JavaScript
/**
* @description Scrimp
* @about Customized CodeMirror 6 editor for use with the Onsight Editor.
* @author Stephens Nunnally <@stevinz>
* @license MIT - Copyright (c) 2024 Stephens Nunnally
* @source https://github.com/onsightengine/scrimp
* @version v0.0.3
*/
class Text {
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);
}
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(from, to, text) {
[from, to] = clip(this, from, to);
let parts = [];
this.decompose(0, from, parts, 2 );
if (text.length)
text.decompose(0, text.length, parts, 1 | 2 );
this.decompose(to, this.length, parts, 1 );
return TextNode.from(parts, this.length - (to - from) + text.length);
}
append(other) {
return this.replace(this.length, this.length, other);
}
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);
}
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;
}
}
iter(dir = 1) { return new RawTextCursor(this, dir); }
iterRange(from, to = this.length) { return new PartialTextCursor(this, from, to); }
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);
}
toString() { return this.sliceString(0); }
toJSON() {
let lines = [];
this.flatten(lines);
return lines;
}
constructor() { }
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 ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, []));
}
}
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(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 ) {
let prev = target.pop();
let joined = appendText(text.text, prev.text.slice(), 0, text.length);
if (joined.length <= 32 ) {
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 )
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 ) {
target.push(new TextLeaf(part, len));
part = [];
len = -1;
}
}
if (len > -1)
target.push(new TextLeaf(part, len));
return target;
}
}
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 : 0) | (end >= to ? 2 : 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;
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 - 1)) &&
updated.lines > (totalLines >> (5 + 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 ) {
let flat = [];
for (let ch of children)
ch.flatten(flat);
return new TextLeaf(flat, length);
}
let chunk = Math.max(32 , lines >> 5 ), 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 ) {
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 = 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) {
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; };
}
class Line {
constructor(
from,
to,
number,
text) {
this.from = from;
this.to = to;
this.number = number;
this.text = text;
}
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))];
}
let extend = "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 = 1; i < extend.length; i++)
extend[i] += extend[i - 1];
function isExtendingChar(code) {
for (let i = 1; i < extend.length; i += 2)
if (extend[i] > code)
return extend[i - 1] <= code;
return false;
}
function isRegionalIndicator(code) {
return code >= 0x1F1E6 && code <= 0x1F1FF;
}
const ZWJ = 0x200d;
function findClusterBreak(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 && surrogateLow(str.charCodeAt(pos)) && surrogateHigh(str.charCodeAt(pos - 1)))
pos--;
let prev = codePointAt(str, pos);
pos += codePointSize(prev);
while (pos < str.length) {
let next = codePointAt(str, pos);
if (prev == ZWJ || next == ZWJ || includeExtending && isExtendingChar(next)) {
pos += codePointSize(next);
prev = next;
}
else if (isRegionalIndicator(next)) {
let countBefore = 0, i = pos - 2;
while (i >= 0 && isRegionalIndicator(codePointAt(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 surrogateLow(ch) { return ch >= 0xDC00 && ch < 0xE000; }
function surrogateHigh(ch) { return ch >= 0xD800 && ch < 0xDC00; }
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;
}
function fromCodePoint(code) {
if (code <= 0xffff)
return String.fromCharCode(code);
code -= 0x10000;
return String.fromCharCode((code >> 10) + 0xd800, (code & 1023) + 0xdc00);
}
function codePointSize(code) { return code < 0x10000 ? 1 : 2; }
const DefaultSplit = /\r\n?|\n/;
var MapMode = (function (MapMode) {
MapMode[MapMode["Simple"] = 0] = "Simple";
MapMode[MapMode["TrackDel"] = 1] = "TrackDel";
MapMode[MapMode["TrackBefore"] = 2] = "TrackBefore";
MapMode[MapMode["TrackAfter"] = 3] = "TrackAfter";
return MapMode})(MapMode || (MapMode = {}));
class ChangeDesc {
constructor(
sections) {
this.sections = sections;
}
get length() {
let result = 0;
for (let i = 0; i < this.sections.length; i += 2)
result += this.sections[i];
return result;
}
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;
}
get empty() { return this.sections.length == 0 || this.sections.length == 2 && this.sections[1] < 0; }
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;
}
}
iterChangedRanges(f, individual = false) {
iterChanges(this, f, individual);
}
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);
}
composeDesc(other) { return this.empty ? other : other.empty ? this : composeSets(this, 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;
}
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;
}
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;
}
toJSON() { return this.sections; }
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);
}
static create(sections) { return new ChangeDesc(sections); }
}
class ChangeSet extends ChangeDesc {
constructor(sections,
inserted) {
super(sections);
this.inserted = inserted;
}
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); }
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);
}
compose(other) { return this.empty ? other : other.empty ? this : composeSets(this, other, true); }
map(other, before = false) { return other.empty ? this : mapSet(this, other, before, true); }
iterChanges(f, individual = false) {
iterChanges(this, f, individual);
}
get desc() { return ChangeDesc.create(this.sections); }
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) };
}
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;
}
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;
}
static empty(length) {
return new ChangeSet(length ? [length, -1] : [], []);
}
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);
}
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 (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) {
let sections = [], insert = mkSet ? [] : null;
let a = new SectionIter(setA), b = new SectionIter(setB);
for (let inserted = -1;;) {
if (a.ins == -1 && b.ins == -1) {
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))) {
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) {
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) {
addSection(sections, a.len, 0, open);
a.next();
}
else if (b.len == 0 && !b.done) {
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;
}
}
}
class SelectionRange {
constructor(
from,
to, flags) {
this.from = from;
this.to = to;
this.flags = flags;
}
get anchor() { return this.flags & 32 ? this.to : this.from; }
get head() { return this.flags & 32 ? this.from : this.to; }
get empty() { return this.from == this.to; }
get assoc() { return this.flags & 8 ? -1 : this.flags & 16 ? 1 : 0; }
get bidiLevel() {
let level = this.flags & 7 ;
return level == 7 ? null : level;
}
get goalColumn() {
let value = this.flags >> 6 ;
return value == 16777215 ? undefined : value;
}
map(change, assoc = -1) {
let from, to;
if (this.empty) {
from = to = change.mapPos(this.from, assoc);
}
else {
from = change.mapPos(this.from, 1);
to = change.mapPos(this.to, -1);
}
return from == this.from && to == this.to ? this : new SelectionRange(from, to, this.flags);
}
extend(from, to = from) {
if (from <= this.anchor && to >= this.anchor)
return EditorSelection.range(from, to);
let head = Math.abs(from - this.anchor) > Math.abs(to - this.anchor) ? from : to;
return EditorSelection.range(this.anchor, head);
}
eq(other, includeAssoc = false) {
return this.anchor == other.anchor && this.head == other.head &&
(!includeAssoc || !this.empty || this.assoc == other.assoc);
}
toJSON() { return { anchor: this.anchor, head: this.head }; }
static fromJSON(json) {
if (!json || typeof json.anchor != "number" || typeof json.head != "number")
throw new RangeError("Invalid JSON representation for SelectionRange");
return EditorSelection.range(json.anchor, json.head);
}
static create(from, to, flags) {
return new SelectionRange(from, to, flags);
}
}
class EditorSelection {
constructor(
ranges,
mainIndex) {
this.ranges = ranges;
this.mainIndex = mainIndex;
}
map(change, assoc = -1) {
if (change.empty)
return this;
return EditorSelection.create(this.ranges.map(r => r.map(change, assoc)), this.mainIndex);
}
eq(other, includeAssoc = false) {
if (this.ranges.length != other.ranges.length ||
this.mainIndex != other.mainIndex)
return false;
for (let i = 0; i < this.ranges.length; i++)
if (!this.ranges[i].eq(other.ranges[i], includeAssoc))
return false;
return true;
}
get main() { return this.ranges[this.mainIndex]; }
asSingle() {
return this.ranges.length == 1 ? this : new EditorSelection([this.main], 0);
}
addRange(range, main = true) {
return EditorSelection.create([range].concat(this.ranges), main ? 0 : this.mainIndex + 1);
}
replaceRange(range, which = this.mainIndex) {
let ranges = this.ranges.slice();
ranges[which] = range;
return EditorSelection.create(ranges, this.mainIndex);
}
toJSON() {
return { ranges: this.ranges.map(r => r.toJSON()), main: this.mainIndex };
}
static fromJSON(json) {
if (!json || !Array.isArray(json.ranges) || typeof json.main != "number" || json.main >= json.ranges.length)
throw new RangeError("Invalid JSON representation for EditorSelection");
return new EditorSelection(json.ranges.map((r) => SelectionRange.fromJSON(r)), json.main);
}
static single(anchor, head = anchor) {
return new EditorSelection([EditorSelection.range(anchor, head)], 0);
}
static create(ranges, mainIndex = 0) {
if (ranges.length == 0)
throw new RangeError("A selection needs at least one range");
for (let pos = 0, i = 0; i < ranges.length; i++) {
let range = ranges[i];
if (range.empty ? range.from <= pos : range.from < pos)
return EditorSelection.normalized(ranges.slice(), mainIndex);
pos = range.to;
}
return new EditorSelection(ranges, mainIndex);
}
static cursor(pos, assoc = 0, bidiLevel, goalColumn) {
return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 8 : 16 ) |
(bidiLevel == null ? 7 : Math.min(6, bidiLevel)) |
((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 ) << 6 ));
}
static range(anchor, head, goalColumn, bidiLevel) {
let flags = ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215 ) << 6 ) |
(bidiLevel == null ? 7 : Math.min(6, bidiLevel));
return head < anchor ? SelectionRange.create(head, anchor, 32 | 16 | flags)
: SelectionRange.create(anchor, head, (head > anchor ? 8 : 0) | flags);
}
static normalized(ranges, mainIndex = 0) {
let main = ranges[mainIndex];
ranges.sort((a, b) => a.from - b.from);
mainIndex = ranges.indexOf(main);
for (let i = 1; i < ranges.length; i++) {
let range = ranges[i], prev = ranges[i - 1];
if (range.empty ? range.from <= prev.to : range.from < prev.to) {
let from = prev.from, to = Math.max(range.to, prev.to);
if (i <= mainIndex)
mainIndex--;
ranges.splice(--i, 2, range.anchor > range.head ? EditorSelection.range(to, from) : EditorSelection.range(from, to));
}
}
return new EditorSelection(ranges, mainIndex);
}
}
function checkSelection(selection, docLength) {
for (let range of selection.ranges)
if (range.to > docLength)
throw new RangeError("Selection points outside of document");
}
let nextID = 0;
class Facet {
constructor(
combine,
compareInput,
compare, isStatic, enables) {
this.combine = combine;
this.compareInput = compareInput;
this.compare = compare;
this.isStatic = isStatic;
this.id = nextID++;
this.default = combine([]);
this.extensions = typeof enables == "function" ? enables(this) : enables;
}
get reader() { return this; }
static define(config = {}) {
return new Facet(config.combine || ((a) => a), config.compareInput || ((a, b) => a === b), config.compare || (!config.combine ? sameArray$1 : (a, b) => a === b), !!config.static, config.enables);
}
of(value) {
return new FacetProvider([], this, 0 , value);
}
compute(deps, get) {
if (this.isStatic)
throw new Error("Can't compute a static facet");
return new FacetProvider(deps, this, 1 , get);
}
computeN(deps, get) {
if (this.isStatic)
throw new Error("Can't compute a static facet");
return new FacetProvider(deps, this, 2 , get);
}
from(field, get) {
if (!get)
get = x => x;
return this.compute([field], state => get(state.field(field)));
}
}
function sameArray$1(a, b) {
return a == b || a.length == b.length && a.every((e, i) => e === b[i]);
}
class FacetProvider {
constructor(dependencies, facet, type, value) {
this.dependencies = dependencies;
this.facet = facet;
this.type = type;
this.value = value;
this.id = nextID++;
}
dynamicSlot(addresses) {
var _a;
let getter = this.value;
let compare = this.facet.compareInput;
let id = this.id, idx = addresses[id] >> 1, multi = this.type == 2 ;
let depDoc = false, depSel = false, depAddrs = [];
for (let dep of this.dependencies) {
if (dep == "doc")
depDoc = true;
else if (dep == "selection")
depSel = true;
else if ((((_a = addresses[dep.id]) !== null && _a !== void 0 ? _a : 1) & 1) == 0)
depAddrs.push(addresses[dep.id]);
}
return {
create(state) {
state.values[idx] = getter(state);
return 1 ;
},
update(state, tr) {
if ((depDoc && tr.docChanged) || (depSel && (tr.docChanged || tr.selection)) || ensureAll(state, depAddrs)) {
let newVal = getter(state);
if (multi ? !compareArray(newVal, state.values[idx], compare) : !compare(newVal, state.values[idx])) {
state.values[idx] = newVal;
return 1 ;
}
}
return 0;
},
reconfigure: (state, oldState) => {
let newVal, oldAddr = oldState.config.address[id];
if (oldAddr != null) {
let oldVal = getAddr(oldState, oldAddr);
if (this.dependencies.every(dep => {
return dep instanceof Facet ? oldState.facet(dep) === state.facet(dep) :
dep instanceof StateField ? oldState.field(dep, false) == state.field(dep, false) : true;
}) || (multi ? compareArray(newVal = getter(state), oldVal, compare) : compare(newVal = getter(state), oldVal))) {
state.values[idx] = oldVal;
return 0;
}
}
else {
newVal = getter(state);
}
state.values[idx] = newVal;
return 1 ;
}
};
}
}
function compareArray(a, b, compare) {
if (a.length != b.length)
return false;
for (let i = 0; i < a.length; i++)
if (!compare(a[i], b[i]))
return false;
return true;
}
function ensureAll(state, addrs) {
let changed = false;
for (let addr of addrs)
if (ensureAddr(state, addr) & 1 )
changed = true;
return changed;
}
function dynamicFacetSlot(addresses, facet, providers) {
let providerAddrs = providers.map(p => addresses[p.id]);
let providerTypes = providers.map(p => p.type);
let dynamic = providerAddrs.filter(p => !(p & 1));
let idx = addresses[facet.id] >> 1;
function get(state) {
let values = [];
for (let i = 0; i < providerAddrs.length; i++) {
let value = getAddr(state, providerAddrs[i]);
if (providerTypes[i] == 2 )
for (let val of value)
values.push(val);
else
values.push(value);
}
return facet.combine(values);
}
return {
create(state) {
for (let addr of providerAddrs)
ensureAddr(state, addr);
state.values[idx] = get(state);
return 1 ;
},
update(state, tr) {
if (!ensureAll(state, dynamic))
return 0;
let value = get(state);
if (facet.compare(value, state.values[idx]))
return 0;
state.values[idx] = value;
return 1 ;
},
reconfigure(state, oldState) {
let depChanged = ensureAll(state, providerAddrs);
let oldProviders = oldState.config.facets[facet.id], oldValue = oldState.facet(facet);
if (oldProviders && !depChanged && sameArray$1(providers, oldProviders)) {
state.values[idx] = oldValue;
re