(function () { 'use strict'; // Compressed representation of the Grapheme_Cluster_Break=Extend // information from // http://www.unicode.org/Public/13.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 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); // Convert offsets into absolute values 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; /// 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, characters /// joined with zero-width joiners, and flag emoji. function findClusterBreak(str, pos, forward = true) { return (forward ? nextClusterBreak : prevClusterBreak)(str, pos); } function nextClusterBreak(str, pos) { if (pos == str.length) return pos; // If pos is in the middle of a surrogate pair, move to its start 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 || 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) { while (pos > 0) { let found = nextClusterBreak(str, pos - 2); if (found < pos) return found; pos--; } return 0; } 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; } /// Given a Unicode codepoint, return the JavaScript string that /// respresents it (like /// [`String.fromCodePoint`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint)). function fromCodePoint(code) { if (code <= 0xffff) return String.fromCharCode(code); code -= 0x10000; return String.fromCharCode((code >> 10) + 0xd800, (code & 1023) + 0xdc00); } /// The first character that takes up two positions in a JavaScript /// string. It is often useful to compare with this after calling /// `codePointAt`, to figure out whether your character takes up 1 or /// 2 index positions. function codePointSize(code) { return code < 0x10000 ? 1 : 2; } /// Count the column position at the given offset into the string, /// taking extending characters and tab size into account. function countColumn(string, n, tabSize) { for (let i = 0; i < string.length;) { if (string.charCodeAt(i) == 9) { n += tabSize - (n % tabSize); i++; } else { n++; i = findClusterBreak(string, i); } } return n; } /// Find the offset that corresponds to the given column position in a /// string, taking extending characters and tab size into account. function findColumn(string, n, col, tabSize) { for (let i = 0; i < string.length;) { if (n >= col) return { offset: i, leftOver: 0 }; n += string.charCodeAt(i) == 9 ? tabSize - (n % tabSize) : 1; i = findClusterBreak(string, i); } return { offset: string.length, leftOver: col - n }; } /// The data structure for documents. class Text { /// @internal constructor() { } /// 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) { let parts = []; this.decompose(0, from, parts, 2 /* To */); if (text.length) text.decompose(0, text.length, parts, 1 /* From */ | 2 /* To */); this.decompose(to, this.length, parts, 1 /* 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) { 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 a = new RawTextCursor(this), b = new RawTextCursor(other); for (;;) { a.next(); b.next(); if (a.lineBreak != b.lineBreak || a.done != b.done || a.value != b.value) return false; if (a.done) 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, and for long lines, might split lines /// themselves into multiple chunks as well. 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); } /// @internal toString() { return this.sliceString(0); } /// Convert the document to an array of lines (which can be /// deserialized again via [`Text.of`](#text.Text^of)). toJSON() { let lines = []; this.flatten(lines); return lines; } /// 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 /* Branch */ ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, [])); } } if (typeof Symbol != "undefined") Text.prototype[Symbol.iterator] = function () { return this.iter(); }; // 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(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 /* From */) { let prev = target.pop(); let joined = appendText(text.text, prev.text.slice(), 0, text.length); if (joined.length <= 32 /* 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); 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 /* Branch */) return new TextLeaf(lines, newLen); return TextNode.from(TextLeaf.split(lines, []), newLen); } sliceString(from, to = this.length, lineSep = "\n") { 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); } static split(text, target) { let part = [], len = -1; for (let line of text) { part.push(line); len += line.length + 1; if (part.length == 32 /* 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 /* From */ : 0) | (end >= to ? 2 /* 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) { 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 /* BranchShift */ - 1)) && updated.lines > (totalLines >> (5 /* 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") { 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); } 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 /* Branch */) { let flat = []; for (let ch of children) ch.flatten(flat); return new TextLeaf(flat, length); } let chunk = Math.max(32 /* Branch */, lines >> 5 /* 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 /* 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 = 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 ? 0 : text instanceof TextLeaf ? text.text.length : text.children.length]; } next(skip = 0) { for (;;) { let last = this.nodes.length - 1; if (last < 0) { this.done = true; this.value = ""; this.lineBreak = false; return this; } let top = this.nodes[last], offset = this.offsets[last]; let size = top instanceof TextLeaf ? top.text.length : top.children.length; if (offset == (this.dir > 0 ? size : 0)) { this.nodes.pop(); this.offsets.pop(); } else if (!this.lineBreak && offset != (this.dir > 0 ? 0 : size)) { // Internal offset with lineBreak == false means we have to // count the line break at this position this.lineBreak = true; if (skip == 0) { this.value = "\n"; return this; } skip--; } else if (top instanceof TextLeaf) { // Move to the next string let next = top.text[offset - (this.dir < 0 ? 1 : 0)]; this.offsets[last] = (offset += this.dir); this.lineBreak = false; if (next.length > Math.max(0, skip)) { this.value = skip == 0 ? next : this.dir > 0 ? next.slice(skip) : next.slice(0, next.length - skip); return this; } skip -= next.length; } else { let next = top.children[this.dir > 0 ? offset : offset - 1]; this.offsets[last] = offset + this.dir; this.lineBreak = false; if (skip > next.length) { skip -= next.length; } else { this.nodes.push(next); this.offsets.push(this.dir > 0 ? 0 : next instanceof TextLeaf ? next.text.length : next.children.length); } } } } } class PartialTextCursor { constructor(text, start, end) { this.value = ""; this.cursor = new RawTextCursor(text, start > end ? -1 : 1); if (start > end) { this.skip = text.length - start; this.limit = start - end; } else { this.skip = start; this.limit = end - start; } } next(skip = 0) { if (this.limit <= 0) { this.limit = -1; } else { let { value, lineBreak, done } = this.cursor.next(this.skip + skip); this.skip = 0; this.value = value; let len = lineBreak ? 1 : value.length; if (len > this.limit) this.value = this.cursor.dir > 0 ? value.slice(0, this.limit) : value.slice(len - this.limit); if (done || this.value.length == 0) this.limit = -1; else this.limit -= this.value.length; } return this; } get lineBreak() { return this.cursor.lineBreak; } get done() { return this.limit < 0; } } /// This type describes a line in the document. It is created /// on-demand when lines are [queried](#text.Text.lineAt). 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; } } const DefaultSplit = /\r\n?|\n/; /// Distinguishes different ways in which positions can be mapped. var MapMode; (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"; })(MapMode || (MapMode = {})); /// A change description is a variant of [change set](#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. 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`](#state.ChangeSet.iterChanges) for a /// variant that also provides you with the inserted text.) /// /// When `individual` is true, adjacent changes (which are kept /// separate for [position mapping](#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 `other` happened before the ones in `this`. 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; } } /// 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 { /// @internal 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. /// /// When `individual` is true, adjacent changes are reported /// separately. iterChanges(f, individual = false) { iterChanges(this, f, individual); } /// Get a [change description](#state.ChangeDesc) for this change /// set. get desc() { return new ChangeDesc(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: new ChangeDesc(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`](#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); } } 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 posA = 0, posB = 0;;) { if (a.ins == -1) { posA += a.len; a.next(); } else if (b.ins == -1 && posB < posA) { let skip = Math.min(b.len, posA - posB); b.forward(skip); addSection(sections, skip, -1); posB += skip; } else if (b.ins >= 0 && (a.done || posB < posA || posB == posA && (b.len < a.len || b.len == a.len && !before))) { addSection(sections, b.ins, -1); while (posA > posB && !a.done && posA + a.len < posB + b.len) { posA += a.len; a.next(); } posB += b.len; b.next(); } else if (a.ins >= 0) { let len = 0, end = posA + a.len; for (;;) { if (b.ins >= 0 && posB > posA && posB + b.len < end) { len += b.ins; posB += b.len; b.next(); } else if (b.ins == -1 && posB < end) { let skip = Math.min(b.len, end - posB); len += skip; b.forward(skip); posB += skip; } else { break; } } addSection(sections, len, a.ins); if (insert) addInsert(insert, sections, a.text); posA = end; a.next(); } else if (a.done && b.done) { return insert ? new ChangeSet(sections, insert) : new ChangeDesc(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 ? new ChangeSet(sections, insert) : new ChangeDesc(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`](#state.EditorState^allowMultipleSelections) /// is enabled, a [selection](#state.EditorSelection) may hold /// multiple ranges. By default, selections hold exactly one range. class SelectionRange { /// @internal 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 & 16 /* Inverted */ ? this.to : this.from; } /// The head of the range, which is moved when the range is /// [extended](#state.SelectionRange.extend). get head() { return this.flags & 16 /* Inverted */ ? this.from : this.to; } /// True when `anchor` and `head` are at the same position. get empty() { return this.from == this.to; } /// If this is a cursor that is explicitly associated with the /// character on one of its sides, this returns the side. -1 means /// the character before its position, 1 the character after, and 0 /// means no association. get assoc() { return this.flags & 4 /* AssocBefore */ ? -1 : this.flags & 8 /* AssocAfter */ ? 1 : 0; } /// The bidirectional text level associated with this cursor, if /// any. get bidiLevel() { let level = this.flags & 3 /* BidiLevelMask */; return level == 3 ? null : level; } /// The goal column (stored vertical offset) associated with a /// cursor. This is used to preserve the vertical position when /// [moving](#view.EditorView.moveVertically) across /// lines of different length. get goalColumn() { let value = this.flags >> 5 /* GoalColumnOffset */; return value == 33554431 /* NoGoalColumn */ ? undefined : value; } /// Map this range through a change, producing a valid range in the /// updated document. map(change) { let from = change.mapPos(this.from), to = change.mapPos(this.to); return from == this.from && to == this.to ? this : new SelectionRange(from, to, this.flags); } /// Extend this range to cover at least `from` to `to`. 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); } /// Compare this range to another range. eq(other) { return this.anchor == other.anchor && this.head == other.head; } /// Return a JSON-serializable object representing the range. toJSON() { return { anchor: this.anchor, head: this.head }; } /// Convert a JSON representation of a range to a `SelectionRange` /// instance. 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); } } /// An editor selection holds one or more selection ranges. class EditorSelection { /// @internal constructor( /// The ranges in the selection, sorted by position. Ranges cannot /// overlap (but they may touch, if they aren't empty). ranges, /// The index of the _main_ range in the selection (which is /// usually the range that was added last). mainIndex = 0) { this.ranges = ranges; this.mainIndex = mainIndex; } /// Map a selection through a change. Used to adjust the selection /// position for changes. map(change) { if (change.empty) return this; return EditorSelection.create(this.ranges.map(r => r.map(change)), this.mainIndex); } /// Compare this selection to another selection. eq(other) { 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])) return false; return true; } /// Get the primary selection range. Usually, you should make sure /// your code applies to _all_ ranges, by using methods like /// [`changeByRange`](#state.EditorState.changeByRange). get main() { return this.ranges[this.mainIndex]; } /// Make sure the selection only has one range. Returns a selection /// holding only the main range from this selection. asSingle() { return this.ranges.length == 1 ? this : new EditorSelection([this.main]); } /// Extend this selection with an extra range. addRange(range, main = true) { return EditorSelection.create([range].concat(this.ranges), main ? 0 : this.mainIndex + 1); } /// Replace a given range with another range, and then normalize the /// selection to merge and sort ranges if necessary. replaceRange(range, which = this.mainIndex) { let ranges = this.ranges.slice(); ranges[which] = range; return EditorSelection.create(ranges, this.mainIndex); } /// Convert this selection to an object that can be serialized to /// JSON. toJSON() { return { ranges: this.ranges.map(r => r.toJSON()), main: this.mainIndex }; } /// Create a selection from a JSON representation. 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); } /// Create a selection holding a single range. static single(anchor, head = anchor) { return new EditorSelection([EditorSelection.range(anchor, head)], 0); } /// Sort and merge the given set of ranges, creating a valid /// selection. 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 normalized(ranges.slice(), mainIndex); pos = range.to; } return new EditorSelection(ranges, mainIndex); } /// Create a cursor selection range at the given position. You can /// safely ignore the optional arguments in most situations. static cursor(pos, assoc = 0, bidiLevel, goalColumn) { return new SelectionRange(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 4 /* AssocBefore */ : 8 /* AssocAfter */) | (bidiLevel == null ? 3 : Math.min(2, bidiLevel)) | ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* NoGoalColumn */) << 5 /* GoalColumnOffset */)); } /// Create a selection range. static range(anchor, head, goalColumn) { let goal = (goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* NoGoalColumn */) << 5 /* GoalColumnOffset */; return head < anchor ? new SelectionRange(head, anchor, 16 /* Inverted */ | goal) : new SelectionRange(anchor, head, goal); } } function 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; /// A facet is a labeled value that is associated with an editor /// state. It takes inputs from any number of extensions, and combines /// those into a single output value. /// /// Examples of facets are the [theme](#view.EditorView^theme) styles /// associated with an editor or the [tab /// size](#state.EditorState^tabSize) (which is reduced to a single /// value, using the input with the hightest precedence). class Facet { constructor( /// @internal combine, /// @internal compareInput, /// @internal compare, isStatic, /// @internal extensions) { this.combine = combine; this.compareInput = compareInput; this.compare = compare; this.isStatic = isStatic; this.extensions = extensions; /// @internal this.id = nextID++; this.default = combine([]); } /// Define a new facet. static define(config = {}) { return new Facet(config.combine || ((a) => a), config.compareInput || ((a, b) => a === b), config.compare || (!config.combine ? sameArray : (a, b) => a === b), !!config.static, config.enables); } /// Returns an extension that adds the given value for this facet. of(value) { return new FacetProvider([], this, 0 /* Static */, value); } /// Create an extension that computes a value for the facet from a /// state. You must take care to declare the parts of the state that /// this value depends on, since your function is only called again /// for a new state when one of those parts changed. /// /// In most cases, you'll want to use the /// [`provide`](#state.StateField^define^config.provide) option when /// defining a field instead. compute(deps, get) { if (this.isStatic) throw new Error("Can't compute a static facet"); return new FacetProvider(deps, this, 1 /* Single */, get); } /// Create an extension that computes zero or more values for this /// facet from a state. computeN(deps, get) { if (this.isStatic) throw new Error("Can't compute a static facet"); return new FacetProvider(deps, this, 2 /* Multi */, get); } from(field, get) { if (!get) get = x => x; return this.compute([field], state => get(state.field(field))); } } function sameArray(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 idx = addresses[this.id] >> 1, multi = this.type == 2 /* Multi */; 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 (state, tr) => { if (!tr || tr.reconfigure) { state.values[idx] = getter(state); return 1 /* Changed */; } else { let depChanged = (depDoc && tr.docChanged) || (depSel && (tr.docChanged || tr.selection)) || depAddrs.some(addr => (ensureAddr(state, addr) & 1 /* Changed */) > 0); if (!depChanged) return 0; let newVal = getter(state), oldVal = tr.startState.values[idx]; if (multi ? compareArray(newVal, oldVal, compare) : compare(newVal, oldVal)) return 0; state.values[idx] = newVal; return 1 /* Changed */; } }; } } 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 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; return (state, tr) => { let oldAddr = !tr ? null : tr.reconfigure ? tr.startState.config.address[facet.id] : idx << 1; let changed = oldAddr == null; for (let dynAddr of dynamic) { if (ensureAddr(state, dynAddr) & 1 /* Changed */) changed = true; } if (!changed) return 0; let values = []; for (let i = 0; i < providerAddrs.length; i++) { let value = getAddr(state, providerAddrs[i]); if (providerTypes[i] == 2 /* Multi */) for (let val of value) values.push(val); else values.push(value); } let newVal = facet.combine(values); if (oldAddr != null && facet.compare(newVal, getAddr(tr.startState, oldAddr))) return 0; state.values[idx] = newVal; return 1 /* Changed */; }; } function maybeIndex(state, id) { let found = state.config.address[id]; return found == null ? null : found >> 1; } const initField = Facet.define({ static: true }); /// Fields can store additional information in an editor state, and /// keep it in sync with the rest of the state. class StateField { constructor( /// @internal id, createF, updateF, compareF, /// @internal spec) { this.id = id; this.createF = createF; this.updateF = updateF; this.compareF = compareF; this.spec = spec; /// @internal this.provides = undefined; } /// Define a state field. static define(config) { let field = new StateField(nextID++, config.create, config.update, config.compare || ((a, b) => a === b), config); if (config.provide) field.provides = config.provide(field); return field; } create(state) { let init = state.facet(initField).find(i => i.field == this); return ((init === null || init === void 0 ? void 0 : init.create) || this.createF)(state); } /// @internal slot(addresses) { let idx = addresses[this.id] >> 1; return (state, tr) => { if (!tr) { state.values[idx] = this.create(state); return 1 /* Changed */; } let oldVal, changed = 0; if (tr.reconfigure) { let oldIdx = maybeIndex(tr.startState, this.id); oldVal = oldIdx == null ? this.create(tr.startState) : tr.startState.values[oldIdx]; changed = 1 /* Changed */; } else { oldVal = tr.startState.values[idx]; } let value = this.updateF(oldVal, tr); if (!changed && !this.compareF(oldVal, value)) changed = 1 /* Changed */; if (changed) state.values[idx] = value; return changed; }; } /// Returns an extension that enables this field and overrides the /// way it is initialized. Can be useful when you need to provide a /// non-default starting value for the field. init(create) { return [this, initField.of({ field: this, create })]; } } const Prec_ = { fallback: 3, default: 2, extend: 1, override: 0 }; function prec(value) { return (ext) => new PrecExtension(ext, value); } /// By default extensions are registered in the order they are found /// in the flattened form of nested array that was provided. /// Individual extension values can be assigned a precedence to /// override this. Extensions that do not have a precedence set get /// the precedence of the nearest parent with a precedence, or /// [`default`](#state.Prec.default) if there is no such parent. The /// final ordering of extensions is determined by first sorting by /// precedence and then by order within each precedence. const Prec = { /// A precedence below the default precedence, which will cause /// default-precedence extensions to override it even if they are /// specified later in the extension ordering. fallback: prec(Prec_.fallback), /// The regular default precedence. default: prec(Prec_.default), /// A higher-than-default precedence. extend: prec(Prec_.extend), /// Precedence above the `default` and `extend` precedences. override: prec(Prec_.override) }; class PrecExtension { constructor(inner, prec) { this.inner = inner; this.prec = prec; } } class TaggedExtension { constructor(tag, inner) { this.tag = tag; this.inner = inner; } } /// Tagged extensions can be used to make a configuration dynamic. /// Tagging an extension allows you to later /// [replace](#state.TransactionSpec.reconfigure) it with /// another extension. A given tag may only occur once within a given /// configuration. function tagExtension(tag, extension) { return new TaggedExtension(tag, extension); } class Configuration { constructor(source, replacements, dynamicSlots, address, staticValues) { this.source = source; this.replacements = replacements; this.dynamicSlots = dynamicSlots; this.address = address; this.staticValues = staticValues; this.statusTemplate = []; while (this.statusTemplate.length < dynamicSlots.length) this.statusTemplate.push(0 /* Uninitialized */); } staticFacet(facet) { let addr = this.address[facet.id]; return addr == null ? facet.default : this.staticValues[addr >> 1]; } static resolve(extension, replacements = Object.create(null), oldState) { let fields = []; let facets = Object.create(null); for (let ext of flatten(extension, replacements)) { if (ext instanceof StateField) fields.push(ext); else (facets[ext.facet.id] || (facets[ext.facet.id] = [])).push(ext); } let address = Object.create(null); let staticValues = []; let dynamicSlots = []; for (let field of fields) { address[field.id] = dynamicSlots.length << 1; dynamicSlots.push(a => field.slot(a)); } for (let id in facets) { let providers = facets[id], facet = providers[0].facet; if (providers.every(p => p.type == 0 /* Static */)) { address[facet.id] = (staticValues.length << 1) | 1; let value = facet.combine(providers.map(p => p.value)); let oldAddr = oldState ? oldState.config.address[facet.id] : null; if (oldAddr != null) { let oldVal = getAddr(oldState, oldAddr); if (facet.compare(value, oldVal)) value = oldVal; } staticValues.push(value); } else { for (let p of providers) { if (p.type == 0 /* Static */) { address[p.id] = (staticValues.length << 1) | 1; staticValues.push(p.value); } else { address[p.id] = dynamicSlots.length << 1; dynamicSlots.push(a => p.dynamicSlot(a)); } } address[facet.id] = dynamicSlots.length << 1; dynamicSlots.push(a => dynamicFacetSlot(a, facet, providers)); } } return new Configuration(extension, replacements, dynamicSlots.map(f => f(address)), address, staticValues); } } function allKeys(obj) { return (Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(obj) : []).concat(Object.keys(obj)); } function flatten(extension, replacements) { let result = [[], [], [], []]; let seen = new Map(); let tagsSeen = Object.create(null); function inner(ext, prec) { let known = seen.get(ext); if (known != null) { if (known >= prec) return; let found = result[known].indexOf(ext); if (found > -1) result[known].splice(found, 1); } seen.set(ext, prec); if (Array.isArray(ext)) { for (let e of ext) inner(e, prec); } else if (ext instanceof TaggedExtension) { if (ext.tag in tagsSeen) throw new RangeError(`Duplicate use of tag '${String(ext.tag)}' in extensions`); tagsSeen[ext.tag] = true; inner(replacements[ext.tag] || ext.inner, prec); } else if (ext instanceof PrecExtension) { inner(ext.inner, ext.prec); } else if (ext instanceof StateField) { result[prec].push(ext); if (ext.provides) inner(ext.provides, prec); } else if (ext instanceof FacetProvider) { result[prec].push(ext); if (ext.facet.extensions) inner(ext.facet.extensions, prec); } else { inner(ext.extension, prec); } } inner(extension, Prec_.default); for (let key of allKeys(replacements)) if (!(key in tagsSeen) && key != "full" && replacements[key]) { tagsSeen[key] = true; inner(replacements[key], Prec_.default); } return result.reduce((a, b) => a.concat(b)); } function ensureAddr(state, addr) { if (addr & 1) return 2 /* Computed */; let idx = addr >> 1; let status = state.status[idx]; if (status == 4 /* Computing */) throw new Error("Cyclic dependency between fields and/or facets"); if (status & 2 /* Computed */) return status; state.status[idx] = 4 /* Computing */; let changed = state.config.dynamicSlots[idx](state, state.applying); return state.status[idx] = 2 /* Computed */ | changed; } function getAddr(state, addr) { return addr & 1 ? state.config.staticValues[addr >> 1] : state.values[addr >> 1]; } const languageData = Facet.define(); const allowMultipleSelections = Facet.define({ combine: values => values.some(v => v), static: true }); const lineSeparator = Facet.define({ combine: values => values.length ? values[0] : undefined, static: true }); const changeFilter = Facet.define(); const transactionFilter = Facet.define(); const transactionExtender = Facet.define(); /// Annotations are tagged values that are used to add metadata to /// transactions in an extensible way. They should be used to model /// things that effect the entire transaction (such as its [time /// stamp](#state.Transaction^time) or information about its /// [origin](#state.Transaction^userEvent)). For effects that happen /// _alongside_ the other changes made by the transaction, [state /// effects](#state.StateEffect) are more appropriate. class Annotation { /// @internal constructor( /// The annotation type. type, /// The value of this annotation. value) { this.type = type; this.value = value; } /// Define a new type of annotation. static define() { return new AnnotationType(); } } /// Marker that identifies a type of [annotation](#state.Annotation). class AnnotationType { /// Create an instance of this annotation. of(value) { return new Annotation(this, value); } } /// State effects can be used to represent additional effects /// associated with a [transaction](#state.Transaction.effects). They /// are often useful to model changes to custom [state /// fields](#state.StateField), when those changes aren't implicit in /// document or selection changes. class StateEffect { /// @internal constructor( /// @internal type, /// The value of this effect. value) { this.type = type; this.value = value; } /// Map this effect through a position mapping. Will return /// `undefined` when that ends up deleting the effect. map(mapping) { let mapped = this.type.map(this.value, mapping); return mapped === undefined ? undefined : mapped == this.value ? this : new StateEffect(this.type, mapped); } /// Tells you whether this effect object is of a given /// [type](#state.StateEffectType). is(type) { return this.type == type; } /// Define a new effect type. The type parameter indicates the type /// of values that his effect holds. static define(spec = {}) { return new StateEffectType(spec.map || (v => v)); } /// Map an array of effects through a change set. static mapEffects(effects, mapping) { if (!effects.length) return effects; let result = []; for (let effect of effects) { let mapped = effect.map(mapping); if (mapped) result.push(mapped); } return result; } } /// Representation of a type of state effect. Defined with /// [`StateEffect.define`](#state.StateEffect^define). class StateEffectType { /// @internal constructor( // The `any` types in these function types are there to work // around TypeScript issue #37631, where the type guard on // `StateEffect.is` mysteriously stops working when these properly // have type `Value`. /// @internal map) { this.map = map; } /// Create a [state effect](#state.StateEffect) instance of this /// type. of(value) { return new StateEffect(this, value); } } /// Changes to the editor state are grouped into transactions. /// Typically, a user action creates a single transaction, which may /// contain any number of document changes, may change the selection, /// or have other effects. Create a transaction by calling /// [`EditorState.update`](#state.EditorState.update). class Transaction { /// @internal constructor( /// The state from which the transaction starts. startState, /// The document changes made by this transaction. changes, /// The selection set by this transaction, or undefined if it /// doesn't explicitly set a selection. selection, /// The effects added to the transaction. effects, /// @internal annotations, /// Holds an object when this transaction /// [reconfigures](#state.ReconfigurationSpec) the state. reconfigure, /// Whether the selection should be scrolled into view after this /// transaction is dispatched. scrollIntoView) { this.startState = startState; this.changes = changes; this.selection = selection; this.effects = effects; this.annotations = annotations; this.reconfigure = reconfigure; this.scrollIntoView = scrollIntoView; /// @internal this._doc = null; /// @internal this._state = null; if (selection) checkSelection(selection, changes.newLength); if (!annotations.some((a) => a.type == Transaction.time)) this.annotations = annotations.concat(Transaction.time.of(Date.now())); } /// The new document produced by the transaction. Contrary to /// [`.state`](#state.Transaction.state)`.doc`, accessing this won't /// force the entire new state to be computed right away, so it is /// recommended that [transaction /// filters](#state.EditorState^transactionFilter) use this getter /// when they need to look at the new document. get newDoc() { return this._doc || (this._doc = this.changes.apply(this.startState.doc)); } /// The new selection produced by the transaction. If /// [`this.selection`](#state.Transaction.selection) is undefined, /// this will [map](#state.EditorSelection.map) the start state's /// current selection through the changes made by the transaction. get newSelection() { return this.selection || this.startState.selection.map(this.changes); } /// The new state created by the transaction. Computed on demand /// (but retained for subsequent access), so itis recommended not to /// access it in [transaction /// filters](#state.EditorState^transactionFilter) when possible. get state() { if (!this._state) this.startState.applyTransaction(this); return this._state; } /// Get the value of the given annotation type, if any. annotation(type) { for (let ann of this.annotations) if (ann.type == type) return ann.value; return undefined; } /// Indicates whether the transaction changed the document. get docChanged() { return !this.changes.empty; } } /// Annotation used to store transaction timestamps. Transaction.time = Annotation.define(); /// Annotation used to associate a transaction with a user interface /// event. The view will set this to... /// /// - `"input"` when the user types text /// - `"delete"` when the user deletes the selection or text near the selection /// - `"keyboardselection"` when moving the selection via the keyboard /// - `"pointerselection"` when moving the selection through the pointing device /// - `"paste"` when pasting content /// - `"cut"` when cutting /// - `"drop"` when content is inserted via drag-and-drop Transaction.userEvent = Annotation.define(); /// Annotation indicating whether a transaction should be added to /// the undo history or not. Transaction.addToHistory = Annotation.define(); function joinRanges(a, b) { let result = []; for (let iA = 0, iB = 0;;) { let from, to; if (iA < a.length && (iB == b.length || b[iB] >= a[iA])) { from = a[iA++]; to = a[iA++]; } else if (iB < b.length) { from = b[iB++]; to = b[iB++]; } else return result; if (!result.length || result[result.length - 1] < from) result.push(from, to); else if (result[result.length - 1] < to) result[result.length - 1] = to; } } function mergeTransaction(a, b, sequential) { var _a; let mapForA, mapForB, changes; if (sequential) { mapForA = b.changes; mapForB = ChangeSet.empty(b.changes.length); changes = a.changes.compose(b.changes); } else { mapForA = b.changes.map(a.changes); mapForB = a.changes.mapDesc(b.changes, true); changes = a.changes.compose(mapForA); } return { changes, selection: b.selection ? b.selection.map(mapForB) : (_a = a.selection) === null || _a === void 0 ? void 0 : _a.map(mapForA), effects: StateEffect.mapEffects(a.effects, mapForA).concat(StateEffect.mapEffects(b.effects, mapForB)), annotations: a.annotations.length ? a.annotations.concat(b.annotations) : b.annotations, scrollIntoView: a.scrollIntoView || b.scrollIntoView, reconfigure: !b.reconfigure ? a.reconfigure : b.reconfigure.full || !a.reconfigure ? b.reconfigure : Object.assign({}, a.reconfigure, b.reconfigure) }; } function resolveTransactionInner(state, spec, docSize) { let reconf = spec.reconfigure; if (reconf && reconf.append) { reconf = Object.assign({}, reconf); let tag = typeof Symbol == "undefined" ? "__append" + Math.floor(Math.random() * 0xffffffff) : Symbol("appendConf"); reconf[tag] = reconf.append; reconf.append = undefined; } let sel = spec.selection; return { changes: spec.changes instanceof ChangeSet ? spec.changes : ChangeSet.of(spec.changes || [], docSize, state.facet(lineSeparator)), selection: sel && (sel instanceof EditorSelection ? sel : EditorSelection.single(sel.anchor, sel.head)), effects: asArray(spec.effects), annotations: asArray(spec.annotations), scrollIntoView: !!spec.scrollIntoView, reconfigure: reconf }; } function resolveTransaction(state, specs, filter) { let s = resolveTransactionInner(state, specs.length ? specs[0] : {}, state.doc.length); if (specs.length && specs[0].filter === false) filter = false; for (let i = 1; i < specs.length; i++) { if (specs[i].filter === false) filter = false; let seq = !!specs[i].sequential; s = mergeTransaction(s, resolveTransactionInner(state, specs[i], seq ? s.changes.newLength : state.doc.length), seq); } let tr = new Transaction(state, s.changes, s.selection, s.effects, s.annotations, s.reconfigure, s.scrollIntoView); return extendTransaction(filter ? filterTransaction(tr) : tr); } // Finish a transaction by applying filters if necessary. function filterTransaction(tr) { let state = tr.startState; // Change filters let result = true; for (let filter of state.facet(changeFilter)) { let value = filter(tr); if (value === false) { result = false; break; } if (Array.isArray(value)) result = result === true ? value : joinRanges(result, value); } if (result !== true) { let changes, back; if (result === false) { back = tr.changes.invertedDesc; changes = ChangeSet.empty(state.doc.length); } else { let filtered = tr.changes.filter(result); changes = filtered.changes; back = filtered.filtered.invertedDesc; } tr = new Transaction(state, changes, tr.selection && tr.selection.map(back), StateEffect.mapEffects(tr.effects, back), tr.annotations, tr.reconfigure, tr.scrollIntoView); } // Transaction filters let filters = state.facet(transactionFilter); for (let i = filters.length - 1; i >= 0; i--) { let filtered = filters[i](tr); if (filtered instanceof Transaction) tr = filtered; else if (Array.isArray(filtered) && filtered.length == 1 && filtered[0] instanceof Transaction) tr = filtered[0]; else tr = resolveTransaction(state, asArray(filtered), false); } return tr; } function extendTransaction(tr) { let state = tr.startState, extenders = state.facet(transactionExtender), spec = tr; for (let i = extenders.length - 1; i >= 0; i--) { let extension = extenders[i](tr); if (extension && Object.keys(extension).length) spec = mergeTransaction(tr, resolveTransactionInner(state, extension, tr.changes.newLength), true); } return spec == tr ? tr : new Transaction(state, tr.changes, tr.selection, spec.effects, spec.annotations, spec.reconfigure, spec.scrollIntoView); } const none = []; function asArray(value) { return value == null ? none : Array.isArray(value) ? value : [value]; } /// The categories produced by a [character /// categorizer](#state.EditorState.charCategorizer). These are used /// do things like selecting by word. var CharCategory; (function (CharCategory) { /// Word characters. CharCategory[CharCategory["Word"] = 0] = "Word"; /// Whitespace. CharCategory[CharCategory["Space"] = 1] = "Space"; /// Anything else. CharCategory[CharCategory["Other"] = 2] = "Other"; })(CharCategory || (CharCategory = {})); const nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; let wordChar; try { wordChar = new RegExp("[\\p{Alphabetic}\\p{Number}_]", "u"); } catch (_) { } function hasWordChar(str) { if (wordChar) return wordChar.test(str); for (let i = 0; i < str.length; i++) { let ch = str[i]; if (/\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))) return true; } return false; } function makeCategorizer(wordChars) { return (char) => { if (!/\S/.test(char)) return CharCategory.Space; if (hasWordChar(char)) return CharCategory.Word; for (let i = 0; i < wordChars.length; i++) if (char.indexOf(wordChars[i]) > -1) return CharCategory.Word; return CharCategory.Other; }; } /// The editor state class is a persistent (immutable) data structure. /// To update a state, you [create](#state.EditorState.update) a /// [transaction](#state.Transaction), which produces a _new_ state /// instance, without modifying the original object. /// /// As such, _never_ mutate properties of a state directly. That'll /// just break things. class EditorState { /// @internal constructor( /// @internal config, /// The current document. doc, /// The current selection. selection, tr = null) { this.config = config; this.doc = doc; this.selection = selection; /// @internal this.applying = null; this.status = config.statusTemplate.slice(); if (tr && !tr.reconfigure) { this.values = tr.startState.values.slice(); } else { this.values = config.dynamicSlots.map(_ => null); // Copy over old values for shared facets/fields if this is a reconfigure if (tr) for (let id in config.address) { let cur = config.address[id], prev = tr.startState.config.address[id]; if (prev != null && (cur & 1) == 0) this.values[cur >> 1] = getAddr(tr.startState, prev); } } this.applying = tr; // Fill in the computed state immediately, so that further queries // for it made during the update return this state if (tr) tr._state = this; for (let i = 0; i < this.config.dynamicSlots.length; i++) ensureAddr(this, i << 1); this.applying = null; } field(field, require = true) { let addr = this.config.address[field.id]; if (addr == null) { if (require) throw new RangeError("Field is not present in this state"); return undefined; } ensureAddr(this, addr); return getAddr(this, addr); } /// Create a [transaction](#state.Transaction) that updates this /// state. Any number of [transaction specs](#state.TransactionSpec) /// can be passed. Unless /// [`sequential`](#state.TransactionSpec.sequential) is set, the /// [changes](#state.TransactionSpec.changes) (if any) of each spec /// are assumed to start in the _current_ document (not the document /// produced by previous specs), and its /// [selection](#state.TransactionSpec.selection) and /// [effects](#state.TransactionSpec.effects) are assumed to refer /// to the document created by its _own_ changes. The resulting /// transaction contains the combined effect of all the different /// specs. For things like /// [selection](#state.TransactionSpec.selection) or /// [reconfiguration](#state.TransactionSpec.reconfigure), later /// specs take precedence over earlier ones. update(...specs) { return resolveTransaction(this, specs, true); } /// @internal applyTransaction(tr) { let conf = this.config; if (tr.reconfigure) conf = Configuration.resolve(tr.reconfigure.full || conf.source, Object.assign(conf.replacements, tr.reconfigure, { full: undefined }), this); new EditorState(conf, tr.newDoc, tr.newSelection, tr); } /// Create a [transaction spec](#state.TransactionSpec) that /// replaces every selection range with the given content. replaceSelection(text) { if (typeof text == "string") text = this.toText(text); return this.changeByRange(range => ({ changes: { from: range.from, to: range.to, insert: text }, range: EditorSelection.cursor(range.from + text.length) })); } /// Create a set of changes and a new selection by running the given /// function for each range in the active selection. The function /// can return an optional set of changes (in the coordinate space /// of the start document), plus an updated range (in the coordinate /// space of the document produced by the call's own changes). This /// method will merge all the changes and ranges into a single /// changeset and selection, and return it as a [transaction /// spec](#state.TransactionSpec), which can be passed to /// [`update`](#state.EditorState.update). changeByRange(f) { let sel = this.selection; let result1 = f(sel.ranges[0]); let changes = this.changes(result1.changes), ranges = [result1.range]; let effects = asArray(result1.effects); for (let i = 1; i < sel.ranges.length; i++) { let result = f(sel.ranges[i]); let newChanges = this.changes(result.changes), newMapped = newChanges.map(changes); for (let j = 0; j < i; j++) ranges[j] = ranges[j].map(newMapped); let mapBy = changes.mapDesc(newChanges, true); ranges.push(result.range.map(mapBy)); changes = changes.compose(newMapped); effects = StateEffect.mapEffects(effects, newMapped).concat(StateEffect.mapEffects(asArray(result.effects), mapBy)); } return { changes, selection: EditorSelection.create(ranges, sel.mainIndex), effects }; } /// Create a [change set](#state.ChangeSet) from the given change /// description, taking the state's document length and line /// separator into account. changes(spec = []) { if (spec instanceof ChangeSet) return spec; return ChangeSet.of(spec, this.doc.length, this.facet(EditorState.lineSeparator)); } /// Using the state's [line /// separator](#state.EditorState^lineSeparator), create a /// [`Text`](#text.Text) instance from the given string. toText(string) { return Text.of(string.split(this.facet(EditorState.lineSeparator) || DefaultSplit)); } /// Return the given range of the document as a string. sliceDoc(from = 0, to = this.doc.length) { return this.doc.sliceString(from, to, this.lineBreak); } /// Get the value of a state [facet](#state.Facet). facet(facet) { let addr = this.config.address[facet.id]; if (addr == null) return facet.default; ensureAddr(this, addr); return getAddr(this, addr); } /// Convert this state to a JSON-serializable object. When custom /// fields should be serialized, you can pass them in as an object /// mapping property names (in the resulting object, which should /// not use `doc` or `selection`) to fields. toJSON(fields) { let result = { doc: this.sliceDoc(), selection: this.selection.toJSON() }; if (fields) for (let prop in fields) result[prop] = fields[prop].spec.toJSON(this.field(fields[prop]), this); return result; } /// Deserialize a state from its JSON representation. When custom /// fields should be deserialized, pass the same object you passed /// to [`toJSON`](#state.EditorState.toJSON) when serializing as /// third argument. static fromJSON(json, config = {}, fields) { if (!json || typeof json.doc != "string") throw new RangeError("Invalid JSON representation for EditorState"); let fieldInit = []; if (fields) for (let prop in fields) { let field = fields[prop], value = json[prop]; fieldInit.push(field.init(state => field.spec.fromJSON(value, state))); } return EditorState.create({ doc: json.doc, selection: EditorSelection.fromJSON(json.selection), extensions: config.extensions ? fieldInit.concat([config.extensions]) : fieldInit }); } /// Create a new state. You'll usually only need this when /// initializing an editor—updated states are created by applying /// transactions. static create(config = {}) { let configuration = Configuration.resolve(config.extensions || []); let doc = config.doc instanceof Text ? config.doc : Text.of((config.doc || "").split(configuration.staticFacet(EditorState.lineSeparator) || DefaultSplit)); let selection = !config.selection ? EditorSelection.single(0) : config.selection instanceof EditorSelection ? config.selection : EditorSelection.single(config.selection.anchor, config.selection.head); checkSelection(selection, doc.length); if (!configuration.staticFacet(allowMultipleSelections)) selection = selection.asSingle(); return new EditorState(configuration, doc, selection); } /// The size (in columns) of a tab in the document, determined by /// the [`tabSize`](#state.EditorState^tabSize) facet. get tabSize() { return this.facet(EditorState.tabSize); } /// Get the proper [line-break](#state.EditorState^lineSeparator) /// string for this state. get lineBreak() { return this.facet(EditorState.lineSeparator) || "\n"; } /// Look up a translation for the given phrase (via the /// [`phrases`](#state.EditorState^phrases) facet), or return the /// original string if no translation is found. phrase(phrase) { for (let map of this.facet(EditorState.phrases)) if (Object.prototype.hasOwnProperty.call(map, phrase)) return map[phrase]; return phrase; } /// Find the values for a given language data field, provided by the /// the [`languageData`](#state.EditorState^languageData) facet. languageDataAt(name, pos) { let values = []; for (let provider of this.facet(languageData)) { for (let result of provider(this, pos)) { if (Object.prototype.hasOwnProperty.call(result, name)) values.push(result[name]); } } return values; } /// Return a function that can categorize strings (expected to /// represent a single [grapheme cluster](#text.findClusterBreak)) /// into one of: /// /// - Word (contains an alphanumeric character or a character /// explicitly listed in the local language's `"wordChars"` /// language data, which should be a string) /// - Space (contains only whitespace) /// - Other (anything else) charCategorizer(at) { return makeCategorizer(this.languageDataAt("wordChars", at).join("")); } } /// A facet that, when enabled, causes the editor to allow multiple /// ranges to be selected. Be careful though, because by default the /// editor relies on the native DOM selection, which cannot handle /// multiple selections. An extension like /// [`drawSelection`](#view.drawSelection) can be used to make /// secondary selections visible to the user. EditorState.allowMultipleSelections = allowMultipleSelections; /// Configures the tab size to use in this state. The first /// (highest-precedence) value of the facet is used. If no value is /// given, this defaults to 4. EditorState.tabSize = Facet.define({ combine: values => values.length ? values[0] : 4 }); /// The line separator to use. By default, any of `"\n"`, `"\r\n"` /// and `"\r"` is treated as a separator when splitting lines, and /// lines are joined with `"\n"`. /// /// When you configure a value here, only that precise separator /// will be used, allowing you to round-trip documents through the /// editor without normalizing line separators. EditorState.lineSeparator = lineSeparator; /// Registers translation phrases. The /// [`phrase`](#state.EditorState.phrase) method will look through /// all objects registered with this facet to find translations for /// its argument. EditorState.phrases = Facet.define(); /// A facet used to register [language /// data](#state.EditorState.languageDataAt) providers. EditorState.languageData = languageData; /// Facet used to register change filters, which are called for each /// transaction (unless explicitly /// [disabled](#state.TransactionSpec.filter)), and can suppress /// part of the transaction's changes. /// /// Such a function can return `true` to indicate that it doesn't /// want to do anything, `false` to completely stop the changes in /// the transaction, or a set of ranges in which changes should be /// suppressed. Such ranges are represented as an array of numbers, /// with each pair of two number indicating the start and end of a /// range. So for example `[10, 20, 100, 110]` suppresses changes /// between 10 and 20, and between 100 and 110. EditorState.changeFilter = changeFilter; /// Facet used to register a hook that gets a chance to update or /// replace transaction specs before they are applied. This will /// only be applied for transactions that don't have /// [`filter`](#state.TransactionSpec.filter) set to `false`. You /// can either return a single (possibly the input transaction), or /// an array of specs (which will be combined in the same way as the /// arguments to [`EditorState.update`](#state.EditorState.update)). /// /// When possible, it is recommended to avoid accessing /// [`Transaction.state`](#state.Transaction.state) in a filter, /// since it will force creation of a state that will then be /// discarded again, if the transaction is actually filtered. /// /// (This functionality should be used with care. Indiscriminately /// modifying transaction is likely to break something or degrade /// the user experience.) EditorState.transactionFilter = transactionFilter; /// This is a more limited form of /// [`transactionFilter`](#state.EditorState^transactionFilter), /// which can only add /// [annotations](#state.TransactionSpec.annotations), /// [effects](#state.TransactionSpec.effects), and /// [configuration](#state.TransactionSpec.reconfigure) info. _But_, /// this type of filter runs even the transaction has disabled /// regular [filtering](#state.TransactionSpec.filter), making it /// suitable for effects that don't need to touch the changes or /// selection, but do want to process every transaction. /// /// Extenders run _after_ filters, when both are applied. EditorState.transactionExtender = transactionExtender; /// Utility function for combining behaviors to fill in a config /// object from an array of provided configs. Will, by default, error /// when a field gets two values that aren't `===`-equal, but you can /// provide combine functions per field to do something else. function combineConfig(configs, defaults, // Should hold only the optional properties of Config, but I haven't managed to express that combine = {}) { let result = {}; for (let config of configs) for (let key of Object.keys(config)) { let value = config[key], current = result[key]; if (current === undefined) result[key] = value; else if (current === value || value === undefined) ; // No conflict else if (Object.hasOwnProperty.call(combine, key)) result[key] = combine[key](current, value); else throw new Error("Config merge conflict for field " + key); } for (let key in defaults) if (result[key] === undefined) result[key] = defaults[key]; return result; } const C = "\u037c"; const COUNT = typeof Symbol == "undefined" ? "__" + C : Symbol.for(C); const SET = typeof Symbol == "undefined" ? "__styleSet" + Math.floor(Math.random() * 1e8) : Symbol("styleSet"); const top = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : {}; // :: - Style modules encapsulate a set of CSS rules defined from // JavaScript. Their definitions are only available in a given DOM // root after it has been _mounted_ there with `StyleModule.mount`. // // Style modules should be created once and stored somewhere, as // opposed to re-creating them every time you need them. The amount of // CSS rules generated for a given DOM root is bounded by the amount // of style modules that were used. So to avoid leaking rules, don't // create these dynamically, but treat them as one-time allocations. class StyleModule { // :: (Object