@codemirror/view 0.19.26 → 0.19.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -655,6 +655,7 @@ class TextView extends ContentView {
655
655
  split(from) {
656
656
  let result = new TextView(this.text.slice(from));
657
657
  this.text = this.text.slice(0, from);
658
+ this.markDirty();
658
659
  return result;
659
660
  }
660
661
  localPosFromDOM(node, offset) {
@@ -842,6 +843,9 @@ class CompositionView extends WidgetView {
842
843
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
843
844
  get isEditable() { return true; }
844
845
  }
846
+ // Use two characters on Android, to prevent Chrome from closing the
847
+ // virtual keyboard when backspacing after a widget (#602).
848
+ const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
845
849
  // These are drawn around uneditable widgets to avoid a number of
846
850
  // browser bugs that show up when the cursor is directly next to
847
851
  // uneditable inline content.
@@ -858,12 +862,13 @@ class WidgetBufferView extends ContentView {
858
862
  split() { return new WidgetBufferView(this.side); }
859
863
  sync() {
860
864
  if (!this.dom)
861
- this.setDOM(document.createTextNode("\u200b"));
862
- else if (this.dirty && this.dom.nodeValue != "\u200b")
863
- this.dom.nodeValue = "\u200b";
865
+ this.setDOM(document.createTextNode(ZeroWidthSpace));
866
+ else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
867
+ this.dom.nodeValue = ZeroWidthSpace;
864
868
  }
865
869
  getSide() { return this.side; }
866
870
  domAtPos(pos) { return DOMPos.before(this.dom); }
871
+ localPosFromDOM() { return 0; }
867
872
  domBoundsAround() { return null; }
868
873
  coordsAt(pos) {
869
874
  let rects = clientRectsFor(this.dom);
@@ -1934,797 +1939,873 @@ class ViewUpdate {
1934
1939
  get empty() { return this.flags == 0 && this.transactions.length == 0; }
1935
1940
  }
1936
1941
 
1937
- class DocView extends ContentView {
1938
- constructor(view) {
1939
- super();
1940
- this.view = view;
1941
- this.compositionDeco = Decoration.none;
1942
- this.decorations = [];
1943
- // Track a minimum width for the editor. When measuring sizes in
1944
- // measureVisibleLineHeights, this is updated to point at the width
1945
- // of a given element and its extent in the document. When a change
1946
- // happens in that range, these are reset. That way, once we've seen
1947
- // a line/element of a given length, we keep the editor wide enough
1948
- // to fit at least that element, until it is changed, at which point
1949
- // we forget it again.
1950
- this.minWidth = 0;
1951
- this.minWidthFrom = 0;
1952
- this.minWidthTo = 0;
1953
- // Track whether the DOM selection was set in a lossy way, so that
1954
- // we don't mess it up when reading it back it
1955
- this.impreciseAnchor = null;
1956
- this.impreciseHead = null;
1957
- this.forceSelection = false;
1958
- // Used by the resize observer to ignore resizes that we caused
1959
- // ourselves
1960
- this.lastUpdate = Date.now();
1961
- this.setDOM(view.contentDOM);
1962
- this.children = [new LineView];
1963
- this.children[0].setParent(this);
1964
- this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
1942
+ /**
1943
+ Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
1944
+ */
1945
+ var Direction = /*@__PURE__*/(function (Direction) {
1946
+ // (These are chosen to match the base levels, in bidi algorithm
1947
+ // terms, of spans in that direction.)
1948
+ /**
1949
+ Left-to-right.
1950
+ */
1951
+ Direction[Direction["LTR"] = 0] = "LTR";
1952
+ /**
1953
+ Right-to-left.
1954
+ */
1955
+ Direction[Direction["RTL"] = 1] = "RTL";
1956
+ return Direction})(Direction || (Direction = {}));
1957
+ const LTR = Direction.LTR, RTL = Direction.RTL;
1958
+ // Decode a string with each type encoded as log2(type)
1959
+ function dec(str) {
1960
+ let result = [];
1961
+ for (let i = 0; i < str.length; i++)
1962
+ result.push(1 << +str[i]);
1963
+ return result;
1964
+ }
1965
+ // Character types for codepoints 0 to 0xf8
1966
+ const LowTypes = /*@__PURE__*/dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
1967
+ // Character types for codepoints 0x600 to 0x6f9
1968
+ const ArabicTypes = /*@__PURE__*/dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
1969
+ const Brackets = /*@__PURE__*/Object.create(null), BracketStack = [];
1970
+ // There's a lot more in
1971
+ // https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
1972
+ // which are left out to keep code size down.
1973
+ for (let p of ["()", "[]", "{}"]) {
1974
+ let l = /*@__PURE__*/p.charCodeAt(0), r = /*@__PURE__*/p.charCodeAt(1);
1975
+ Brackets[l] = r;
1976
+ Brackets[r] = -l;
1977
+ }
1978
+ function charType(ch) {
1979
+ return ch <= 0xf7 ? LowTypes[ch] :
1980
+ 0x590 <= ch && ch <= 0x5f4 ? 2 /* R */ :
1981
+ 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
1982
+ 0x6ee <= ch && ch <= 0x8ac ? 4 /* AL */ :
1983
+ 0x2000 <= ch && ch <= 0x200b ? 256 /* NI */ :
1984
+ ch == 0x200c ? 256 /* NI */ : 1 /* L */;
1985
+ }
1986
+ const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
1987
+ /**
1988
+ Represents a contiguous range of text that has a single direction
1989
+ (as in left-to-right or right-to-left).
1990
+ */
1991
+ class BidiSpan {
1992
+ /**
1993
+ @internal
1994
+ */
1995
+ constructor(
1996
+ /**
1997
+ The start of the span (relative to the start of the line).
1998
+ */
1999
+ from,
2000
+ /**
2001
+ The end of the span.
2002
+ */
2003
+ to,
2004
+ /**
2005
+ The ["bidi
2006
+ level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
2007
+ of the span (in this context, 0 means
2008
+ left-to-right, 1 means right-to-left, 2 means left-to-right
2009
+ number inside right-to-left text).
2010
+ */
2011
+ level) {
2012
+ this.from = from;
2013
+ this.to = to;
2014
+ this.level = level;
1965
2015
  }
1966
- get root() { return this.view.root; }
1967
- get editorView() { return this.view; }
1968
- get length() { return this.view.state.doc.length; }
1969
- // Update the document view to a given state. scrollIntoView can be
1970
- // used as a hint to compute a new viewport that includes that
1971
- // position, if we know the editor is going to scroll that position
1972
- // into view.
1973
- update(update) {
1974
- let changedRanges = update.changedRanges;
1975
- if (this.minWidth > 0 && changedRanges.length) {
1976
- if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
1977
- this.minWidth = 0;
1978
- }
1979
- else {
1980
- this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
1981
- this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2016
+ /**
2017
+ The direction of this span.
2018
+ */
2019
+ get dir() { return this.level % 2 ? RTL : LTR; }
2020
+ /**
2021
+ @internal
2022
+ */
2023
+ side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
2024
+ /**
2025
+ @internal
2026
+ */
2027
+ static find(order, index, level, assoc) {
2028
+ let maybe = -1;
2029
+ for (let i = 0; i < order.length; i++) {
2030
+ let span = order[i];
2031
+ if (span.from <= index && span.to >= index) {
2032
+ if (span.level == level)
2033
+ return i;
2034
+ // When multiple spans match, if assoc != 0, take the one that
2035
+ // covers that side, otherwise take the one with the minimum
2036
+ // level.
2037
+ if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
2038
+ maybe = i;
1982
2039
  }
1983
2040
  }
1984
- if (this.view.inputState.composing < 0)
1985
- this.compositionDeco = Decoration.none;
1986
- else if (update.transactions.length)
1987
- this.compositionDeco = computeCompositionDeco(this.view, update.changes);
1988
- // When the DOM nodes around the selection are moved to another
1989
- // parent, Chrome sometimes reports a different selection through
1990
- // getSelection than the one that it actually shows to the user.
1991
- // This forces a selection update when lines are joined to work
1992
- // around that. Issue #54
1993
- if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1994
- update.state.doc.lines != update.startState.doc.lines)
1995
- this.forceSelection = true;
1996
- let prevDeco = this.decorations, deco = this.updateDeco();
1997
- let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1998
- changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1999
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
2000
- return false;
2001
- }
2002
- else {
2003
- this.updateInner(changedRanges, deco, update.startState.doc.length);
2004
- if (update.transactions.length)
2005
- this.lastUpdate = Date.now();
2006
- return true;
2007
- }
2041
+ if (maybe < 0)
2042
+ throw new RangeError("Index out of range");
2043
+ return maybe;
2008
2044
  }
2009
- reset(sel) {
2010
- if (this.dirty) {
2011
- this.view.observer.ignore(() => this.view.docView.sync());
2012
- this.dirty = 0 /* Not */;
2013
- this.updateSelection(true);
2045
+ }
2046
+ // Reused array of character types
2047
+ const types = [];
2048
+ function computeOrder(line, direction) {
2049
+ let len = line.length, outerType = direction == LTR ? 1 /* L */ : 2 /* R */, oppositeType = direction == LTR ? 2 /* R */ : 1 /* L */;
2050
+ if (!line || outerType == 1 /* L */ && !BidiRE.test(line))
2051
+ return trivialOrder(len);
2052
+ // W1. Examine each non-spacing mark (NSM) in the level run, and
2053
+ // change the type of the NSM to the type of the previous
2054
+ // character. If the NSM is at the start of the level run, it will
2055
+ // get the type of sor.
2056
+ // W2. Search backwards from each instance of a European number
2057
+ // until the first strong type (R, L, AL, or sor) is found. If an
2058
+ // AL is found, change the type of the European number to Arabic
2059
+ // number.
2060
+ // W3. Change all ALs to R.
2061
+ // (Left after this: L, R, EN, AN, ET, CS, NI)
2062
+ for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2063
+ let type = charType(line.charCodeAt(i));
2064
+ if (type == 512 /* NSM */)
2065
+ type = prev;
2066
+ else if (type == 8 /* EN */ && prevStrong == 4 /* AL */)
2067
+ type = 16 /* AN */;
2068
+ types[i] = type == 4 /* AL */ ? 2 /* R */ : type;
2069
+ if (type & 7 /* Strong */)
2070
+ prevStrong = type;
2071
+ prev = type;
2072
+ }
2073
+ // W5. A sequence of European terminators adjacent to European
2074
+ // numbers changes to all European numbers.
2075
+ // W6. Otherwise, separators and terminators change to Other
2076
+ // Neutral.
2077
+ // W7. Search backwards from each instance of a European number
2078
+ // until the first strong type (R, L, or sor) is found. If an L is
2079
+ // found, then change the type of the European number to L.
2080
+ // (Left after this: L, R, EN+AN, NI)
2081
+ for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2082
+ let type = types[i];
2083
+ if (type == 128 /* CS */) {
2084
+ if (i < len - 1 && prev == types[i + 1] && (prev & 24 /* Num */))
2085
+ type = types[i] = prev;
2086
+ else
2087
+ types[i] = 256 /* NI */;
2014
2088
  }
2015
- else {
2016
- this.updateSelection();
2089
+ else if (type == 64 /* ET */) {
2090
+ let end = i + 1;
2091
+ while (end < len && types[end] == 64 /* ET */)
2092
+ end++;
2093
+ let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2094
+ for (let j = i; j < end; j++)
2095
+ types[j] = replace;
2096
+ i = end - 1;
2017
2097
  }
2098
+ else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2099
+ types[i] = 1 /* L */;
2100
+ }
2101
+ prev = type;
2102
+ if (type & 7 /* Strong */)
2103
+ prevStrong = type;
2018
2104
  }
2019
- // Used by update and the constructor do perform the actual DOM
2020
- // update
2021
- updateInner(changes, deco, oldLength) {
2022
- this.view.viewState.mustMeasureContent = true;
2023
- this.updateChildren(changes, deco, oldLength);
2024
- let { observer } = this.view;
2025
- observer.ignore(() => {
2026
- // Lock the height during redrawing, since Chrome sometimes
2027
- // messes with the scroll position during DOM mutation (though
2028
- // no relayout is triggered and I cannot imagine how it can
2029
- // recompute the scroll position without a layout)
2030
- this.dom.style.height = this.view.viewState.contentHeight + "px";
2031
- this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2032
- // Chrome will sometimes, when DOM mutations occur directly
2033
- // around the selection, get confused and report a different
2034
- // selection from the one it displays (issue #218). This tries
2035
- // to detect that situation.
2036
- let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2037
- this.sync(track);
2038
- this.dirty = 0 /* Not */;
2039
- if (track && (track.written || observer.selectionRange.focusNode != track.node))
2040
- this.forceSelection = true;
2041
- this.dom.style.height = "";
2042
- });
2043
- let gaps = [];
2044
- if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2045
- for (let child of this.children)
2046
- if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2047
- gaps.push(child.dom);
2048
- observer.updateGaps(gaps);
2049
- }
2050
- updateChildren(changes, deco, oldLength) {
2051
- let cursor = this.childCursor(oldLength);
2052
- for (let i = changes.length - 1;; i--) {
2053
- let next = i >= 0 ? changes[i] : null;
2054
- if (!next)
2055
- break;
2056
- let { fromA, toA, fromB, toB } = next;
2057
- let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2058
- let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2059
- let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2060
- replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2061
- }
2062
- }
2063
- // Sync the DOM selection to this.state.selection
2064
- updateSelection(mustRead = false, fromPointer = false) {
2065
- if (mustRead)
2066
- this.view.observer.readSelectionRange();
2067
- if (!(fromPointer || this.mayControlSelection()) ||
2068
- browser.ios && this.view.inputState.rapidCompositionStart)
2069
- return;
2070
- let force = this.forceSelection;
2071
- this.forceSelection = false;
2072
- let main = this.view.state.selection.main;
2073
- // FIXME need to handle the case where the selection falls inside a block range
2074
- let anchor = this.domAtPos(main.anchor);
2075
- let head = main.empty ? anchor : this.domAtPos(main.head);
2076
- // Always reset on Firefox when next to an uneditable node to
2077
- // avoid invisible cursor bugs (#111)
2078
- if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2079
- let dummy = document.createTextNode("");
2080
- this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2081
- anchor = head = new DOMPos(dummy, 0);
2082
- force = true;
2083
- }
2084
- let domSel = this.view.observer.selectionRange;
2085
- // If the selection is already here, or in an equivalent position, don't touch it
2086
- if (force || !domSel.focusNode ||
2087
- !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2088
- !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2089
- this.view.observer.ignore(() => {
2090
- // Chrome Android will hide the virtual keyboard when tapping
2091
- // inside an uneditable node, and not bring it back when we
2092
- // move the cursor to its proper position. This tries to
2093
- // restore the keyboard by cycling focus.
2094
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2095
- this.dom.blur();
2096
- this.dom.focus({ preventScroll: true });
2097
- }
2098
- let rawSel = getSelection(this.root);
2099
- if (main.empty) {
2100
- // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2101
- if (browser.gecko) {
2102
- let nextTo = nextToUneditable(anchor.node, anchor.offset);
2103
- if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2104
- let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2105
- if (text)
2106
- anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2107
- }
2105
+ // N0. Process bracket pairs in an isolating run sequence
2106
+ // sequentially in the logical order of the text positions of the
2107
+ // opening paired brackets using the logic given below. Within this
2108
+ // scope, bidirectional types EN and AN are treated as R.
2109
+ for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2110
+ // Keeps [startIndex, type, strongSeen] triples for each open
2111
+ // bracket on BracketStack.
2112
+ if (br = Brackets[ch = line.charCodeAt(i)]) {
2113
+ if (br < 0) { // Closing bracket
2114
+ for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2115
+ if (BracketStack[sJ + 1] == -br) {
2116
+ let flags = BracketStack[sJ + 2];
2117
+ let type = (flags & 2 /* EmbedInside */) ? outerType :
2118
+ !(flags & 4 /* OppositeInside */) ? 0 :
2119
+ (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2120
+ if (type)
2121
+ types[i] = types[BracketStack[sJ]] = type;
2122
+ sI = sJ;
2123
+ break;
2108
2124
  }
2109
- rawSel.collapse(anchor.node, anchor.offset);
2110
- if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2111
- domSel.cursorBidiLevel = main.bidiLevel;
2112
2125
  }
2113
- else if (rawSel.extend) {
2114
- // Selection.extend can be used to create an 'inverted' selection
2115
- // (one where the focus is before the anchor), but not all
2116
- // browsers support it yet.
2117
- rawSel.collapse(anchor.node, anchor.offset);
2118
- rawSel.extend(head.node, head.offset);
2126
+ }
2127
+ else if (BracketStack.length == 189 /* MaxDepth */) {
2128
+ break;
2129
+ }
2130
+ else {
2131
+ BracketStack[sI++] = i;
2132
+ BracketStack[sI++] = ch;
2133
+ BracketStack[sI++] = context;
2134
+ }
2135
+ }
2136
+ else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2137
+ let embed = type == outerType;
2138
+ context = embed ? 0 : 1 /* OppositeBefore */;
2139
+ for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2140
+ let cur = BracketStack[sJ + 2];
2141
+ if (cur & 2 /* EmbedInside */)
2142
+ break;
2143
+ if (embed) {
2144
+ BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2119
2145
  }
2120
2146
  else {
2121
- // Primitive (IE) way
2122
- let range = document.createRange();
2123
- if (main.anchor > main.head)
2124
- [anchor, head] = [head, anchor];
2125
- range.setEnd(head.node, head.offset);
2126
- range.setStart(anchor.node, anchor.offset);
2127
- rawSel.removeAllRanges();
2128
- rawSel.addRange(range);
2147
+ if (cur & 4 /* OppositeInside */)
2148
+ break;
2149
+ BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2129
2150
  }
2130
- });
2131
- this.view.observer.setSelectionRange(anchor, head);
2151
+ }
2132
2152
  }
2133
- this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2134
- this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2135
- }
2136
- enforceCursorAssoc() {
2137
- if (this.view.composing)
2138
- return;
2139
- let cursor = this.view.state.selection.main;
2140
- let sel = getSelection(this.root);
2141
- if (!cursor.empty || !cursor.assoc || !sel.modify)
2142
- return;
2143
- let line = LineView.find(this, cursor.head);
2144
- if (!line)
2145
- return;
2146
- let lineStart = line.posAtStart;
2147
- if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2148
- return;
2149
- let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2150
- if (!before || !after || before.bottom > after.top)
2151
- return;
2152
- let dom = this.domAtPos(cursor.head + cursor.assoc);
2153
- sel.collapse(dom.node, dom.offset);
2154
- sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2155
- }
2156
- mayControlSelection() {
2157
- return this.view.state.facet(editable) ? this.root.activeElement == this.dom
2158
- : hasSelection(this.dom, this.view.observer.selectionRange);
2159
2153
  }
2160
- nearest(dom) {
2161
- for (let cur = dom; cur;) {
2162
- let domView = ContentView.get(cur);
2163
- if (domView && domView.rootView == this)
2164
- return domView;
2165
- cur = cur.parentNode;
2154
+ // N1. A sequence of neutrals takes the direction of the
2155
+ // surrounding strong text if the text on both sides has the same
2156
+ // direction. European and Arabic numbers act as if they were R in
2157
+ // terms of their influence on neutrals. Start-of-level-run (sor)
2158
+ // and end-of-level-run (eor) are used at level run boundaries.
2159
+ // N2. Any remaining neutrals take the embedding direction.
2160
+ // (Left after this: L, R, EN+AN)
2161
+ for (let i = 0; i < len; i++) {
2162
+ if (types[i] == 256 /* NI */) {
2163
+ let end = i + 1;
2164
+ while (end < len && types[end] == 256 /* NI */)
2165
+ end++;
2166
+ let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2167
+ let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2168
+ let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2169
+ for (let j = i; j < end; j++)
2170
+ types[j] = replace;
2171
+ i = end - 1;
2166
2172
  }
2167
- return null;
2168
2173
  }
2169
- posFromDOM(node, offset) {
2170
- let view = this.nearest(node);
2171
- if (!view)
2172
- throw new RangeError("Trying to find position for a DOM position outside of the document");
2173
- return view.localPosFromDOM(node, offset) + view.posAtStart;
2174
+ // Here we depart from the documented algorithm, in order to avoid
2175
+ // building up an actual levels array. Since there are only three
2176
+ // levels (0, 1, 2) in an implementation that doesn't take
2177
+ // explicit embedding into account, we can build up the order on
2178
+ // the fly, without following the level-based algorithm.
2179
+ let order = [];
2180
+ if (outerType == 1 /* L */) {
2181
+ for (let i = 0; i < len;) {
2182
+ let start = i, rtl = types[i++] != 1 /* L */;
2183
+ while (i < len && rtl == (types[i] != 1 /* L */))
2184
+ i++;
2185
+ if (rtl) {
2186
+ for (let j = i; j > start;) {
2187
+ let end = j, l = types[--j] != 2 /* R */;
2188
+ while (j > start && l == (types[j - 1] != 2 /* R */))
2189
+ j--;
2190
+ order.push(new BidiSpan(j, end, l ? 2 : 1));
2191
+ }
2192
+ }
2193
+ else {
2194
+ order.push(new BidiSpan(start, i, 0));
2195
+ }
2196
+ }
2174
2197
  }
2175
- domAtPos(pos) {
2176
- let { i, off } = this.childCursor().findPos(pos, -1);
2177
- for (; i < this.children.length - 1;) {
2178
- let child = this.children[i];
2179
- if (off < child.length || child instanceof LineView)
2180
- break;
2181
- i++;
2182
- off = 0;
2198
+ else {
2199
+ for (let i = 0; i < len;) {
2200
+ let start = i, rtl = types[i++] == 2 /* R */;
2201
+ while (i < len && rtl == (types[i] == 2 /* R */))
2202
+ i++;
2203
+ order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2183
2204
  }
2184
- return this.children[i].domAtPos(off);
2185
2205
  }
2186
- coordsAt(pos, side) {
2187
- for (let off = this.length, i = this.children.length - 1;; i--) {
2188
- let child = this.children[i], start = off - child.breakAfter - child.length;
2189
- if (pos > start ||
2190
- (pos == start && child.type != BlockType.WidgetBefore && child.type != BlockType.WidgetAfter &&
2191
- (!i || side == 2 || this.children[i - 1].breakAfter ||
2192
- (this.children[i - 1].type == BlockType.WidgetBefore && side > -2))))
2193
- return child.coordsAt(pos - start, side);
2194
- off = start;
2206
+ return order;
2207
+ }
2208
+ function trivialOrder(length) {
2209
+ return [new BidiSpan(0, length, 0)];
2210
+ }
2211
+ let movedOver = "";
2212
+ function moveVisually(line, order, dir, start, forward) {
2213
+ var _a;
2214
+ let startIndex = start.head - line.from, spanI = -1;
2215
+ if (startIndex == 0) {
2216
+ if (!forward || !line.length)
2217
+ return null;
2218
+ if (order[0].level != dir) {
2219
+ startIndex = order[0].side(false, dir);
2220
+ spanI = 0;
2195
2221
  }
2196
2222
  }
2197
- measureVisibleLineHeights() {
2198
- let result = [], { from, to } = this.view.viewState.viewport;
2199
- let minWidth = Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2200
- for (let pos = 0, i = 0; i < this.children.length; i++) {
2201
- let child = this.children[i], end = pos + child.length;
2202
- if (end > to)
2203
- break;
2204
- if (pos >= from) {
2205
- result.push(child.dom.getBoundingClientRect().height);
2206
- let width = child.dom.scrollWidth;
2207
- if (width > minWidth) {
2208
- this.minWidth = minWidth = width;
2209
- this.minWidthFrom = pos;
2210
- this.minWidthTo = end;
2211
- }
2212
- }
2213
- pos = end + child.breakAfter;
2223
+ else if (startIndex == line.length) {
2224
+ if (forward)
2225
+ return null;
2226
+ let last = order[order.length - 1];
2227
+ if (last.level != dir) {
2228
+ startIndex = last.side(true, dir);
2229
+ spanI = order.length - 1;
2214
2230
  }
2215
- return result;
2216
2231
  }
2217
- measureTextSize() {
2218
- for (let child of this.children) {
2219
- if (child instanceof LineView) {
2220
- let measure = child.measureTextSize();
2221
- if (measure)
2222
- return measure;
2223
- }
2224
- }
2225
- // If no workable line exists, force a layout of a measurable element
2226
- let dummy = document.createElement("div"), lineHeight, charWidth;
2227
- dummy.className = "cm-line";
2228
- dummy.textContent = "abc def ghi jkl mno pqr stu";
2229
- this.view.observer.ignore(() => {
2230
- this.dom.appendChild(dummy);
2231
- let rect = clientRectsFor(dummy.firstChild)[0];
2232
- lineHeight = dummy.getBoundingClientRect().height;
2233
- charWidth = rect ? rect.width / 27 : 7;
2234
- dummy.remove();
2235
- });
2236
- return { lineHeight, charWidth };
2232
+ if (spanI < 0)
2233
+ spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2234
+ let span = order[spanI];
2235
+ // End of span. (But not end of line--that was checked for above.)
2236
+ if (startIndex == span.side(forward, dir)) {
2237
+ span = order[spanI += forward ? 1 : -1];
2238
+ startIndex = span.side(!forward, dir);
2237
2239
  }
2238
- childCursor(pos = this.length) {
2239
- // Move back to start of last element when possible, so that
2240
- // `ChildCursor.findPos` doesn't have to deal with the edge case
2241
- // of being after the last element.
2242
- let i = this.children.length;
2243
- if (i)
2244
- pos -= this.children[--i].length;
2245
- return new ChildCursor(this.children, pos, i);
2240
+ let indexForward = forward == (span.dir == dir);
2241
+ let nextIndex = findClusterBreak(line.text, startIndex, indexForward);
2242
+ movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2243
+ if (nextIndex != span.side(forward, dir))
2244
+ return EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2245
+ let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2246
+ if (!nextSpan && span.level != dir)
2247
+ return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2248
+ if (nextSpan && nextSpan.level < span.level)
2249
+ return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2250
+ return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2251
+ }
2252
+
2253
+ class DOMReader {
2254
+ constructor(points, view) {
2255
+ this.points = points;
2256
+ this.view = view;
2257
+ this.text = "";
2258
+ this.lineBreak = view.state.lineBreak;
2246
2259
  }
2247
- computeBlockGapDeco() {
2248
- let deco = [], vs = this.view.viewState;
2249
- for (let pos = 0, i = 0;; i++) {
2250
- let next = i == vs.viewports.length ? null : vs.viewports[i];
2251
- let end = next ? next.from - 1 : this.length;
2252
- if (end > pos) {
2253
- let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2254
- deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2255
- }
2256
- if (!next)
2260
+ readRange(start, end) {
2261
+ if (!start)
2262
+ return this;
2263
+ let parent = start.parentNode;
2264
+ for (let cur = start;;) {
2265
+ this.findPointBefore(parent, cur);
2266
+ this.readNode(cur);
2267
+ let next = cur.nextSibling;
2268
+ if (next == end)
2257
2269
  break;
2258
- pos = next.to + 1;
2270
+ let view = ContentView.get(cur), nextView = ContentView.get(next);
2271
+ if (view && nextView ? view.breakAfter :
2272
+ (view ? view.breakAfter : isBlockElement(cur)) ||
2273
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2274
+ this.text += this.lineBreak;
2275
+ cur = next;
2259
2276
  }
2260
- return Decoration.set(deco);
2261
- }
2262
- updateDeco() {
2263
- return this.decorations = [
2264
- ...this.view.pluginField(PluginField.decorations),
2265
- ...this.view.state.facet(decorations),
2266
- this.compositionDeco,
2267
- this.computeBlockGapDeco(),
2268
- this.view.viewState.lineGapDeco
2269
- ];
2277
+ this.findPointBefore(parent, end);
2278
+ return this;
2270
2279
  }
2271
- scrollIntoView({ range, center }) {
2272
- let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2273
- if (!rect)
2280
+ readNode(node) {
2281
+ if (node.cmIgnore)
2274
2282
  return;
2275
- if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2276
- rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2277
- right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2278
- let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2279
- for (let margins of this.view.pluginField(PluginField.scrollMargins))
2280
- if (margins) {
2281
- let { left, right, top, bottom } = margins;
2282
- if (left != null)
2283
- mLeft = Math.max(mLeft, left);
2284
- if (right != null)
2285
- mRight = Math.max(mRight, right);
2286
- if (top != null)
2287
- mTop = Math.max(mTop, top);
2288
- if (bottom != null)
2289
- mBottom = Math.max(mBottom, bottom);
2290
- }
2291
- scrollRectIntoView(this.view.scrollDOM, {
2292
- left: rect.left - mLeft, top: rect.top - mTop,
2293
- right: rect.right + mRight, bottom: rect.bottom + mBottom
2294
- }, range.head < range.anchor ? -1 : 1, center);
2295
- }
2296
- }
2297
- function betweenUneditable(pos) {
2298
- return pos.node.nodeType == 1 && pos.node.firstChild &&
2299
- (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2300
- (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2301
- }
2302
- class BlockGapWidget extends WidgetType {
2303
- constructor(height) {
2304
- super();
2305
- this.height = height;
2306
- }
2307
- toDOM() {
2308
- let elt = document.createElement("div");
2309
- this.updateDOM(elt);
2310
- return elt;
2311
- }
2312
- eq(other) { return other.height == this.height; }
2313
- updateDOM(elt) {
2314
- elt.style.height = this.height + "px";
2315
- return true;
2316
- }
2317
- get estimatedHeight() { return this.height; }
2318
- }
2319
- function computeCompositionDeco(view, changes) {
2320
- let sel = view.observer.selectionRange;
2321
- let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2322
- if (!textNode)
2323
- return Decoration.none;
2324
- let cView = view.docView.nearest(textNode);
2325
- if (!cView)
2326
- return Decoration.none;
2327
- let from, to, topNode = textNode;
2328
- if (cView instanceof LineView) {
2329
- while (topNode.parentNode != cView.dom)
2330
- topNode = topNode.parentNode;
2331
- let prev = topNode.previousSibling;
2332
- while (prev && !ContentView.get(prev))
2333
- prev = prev.previousSibling;
2334
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2335
- }
2336
- else {
2337
- for (;;) {
2338
- let { parent } = cView;
2339
- if (!parent)
2340
- return Decoration.none;
2341
- if (parent instanceof LineView)
2342
- break;
2343
- cView = parent;
2283
+ let view = ContentView.get(node);
2284
+ let fromView = view && view.overrideDOMText;
2285
+ let text;
2286
+ if (fromView != null)
2287
+ text = fromView.sliceString(0, undefined, this.lineBreak);
2288
+ else if (node.nodeType == 3)
2289
+ text = node.nodeValue;
2290
+ else if (node.nodeName == "BR")
2291
+ text = node.nextSibling ? this.lineBreak : "";
2292
+ else if (node.nodeType == 1)
2293
+ this.readRange(node.firstChild, null);
2294
+ if (text != null) {
2295
+ this.findPointIn(node, text.length);
2296
+ this.text += text;
2297
+ // Chrome inserts two newlines when pressing shift-enter at the
2298
+ // end of a line. This drops one of those.
2299
+ if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2300
+ this.text = this.text.slice(0, -1);
2344
2301
  }
2345
- from = cView.posAtStart;
2346
- to = from + cView.length;
2347
- topNode = cView.dom;
2348
- }
2349
- let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2350
- let text = textNode.nodeValue, { state } = view;
2351
- if (newTo - newFrom < text.length) {
2352
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2353
- newTo = newFrom + text.length;
2354
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2355
- newFrom = newTo - text.length;
2356
- else
2357
- return Decoration.none;
2358
2302
  }
2359
- else if (state.sliceDoc(newFrom, newTo) != text) {
2360
- return Decoration.none;
2361
- }
2362
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2363
- }
2364
- class CompositionWidget extends WidgetType {
2365
- constructor(top, text) {
2366
- super();
2367
- this.top = top;
2368
- this.text = text;
2303
+ findPointBefore(node, next) {
2304
+ for (let point of this.points)
2305
+ if (point.node == node && node.childNodes[point.offset] == next)
2306
+ point.pos = this.text.length;
2369
2307
  }
2370
- eq(other) { return this.top == other.top && this.text == other.text; }
2371
- toDOM() { return this.top; }
2372
- ignoreEvent() { return false; }
2373
- get customView() { return CompositionView; }
2374
- }
2375
- function nearbyTextNode(node, offset, side) {
2376
- for (;;) {
2377
- if (node.nodeType == 3)
2378
- return node;
2379
- if (node.nodeType == 1 && offset > 0 && side <= 0) {
2380
- node = node.childNodes[offset - 1];
2381
- offset = maxOffset(node);
2382
- }
2383
- else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2384
- node = node.childNodes[offset];
2385
- offset = 0;
2386
- }
2387
- else {
2388
- return null;
2389
- }
2308
+ findPointIn(node, maxLen) {
2309
+ for (let point of this.points)
2310
+ if (point.node == node)
2311
+ point.pos = this.text.length + Math.min(point.offset, maxLen);
2390
2312
  }
2391
2313
  }
2392
- function nextToUneditable(node, offset) {
2393
- if (node.nodeType != 1)
2394
- return 0;
2395
- return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2396
- (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2314
+ function isBlockElement(node) {
2315
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
2397
2316
  }
2398
- class DecorationComparator$1 {
2399
- constructor() {
2400
- this.changes = [];
2317
+ class DOMPoint {
2318
+ constructor(node, offset) {
2319
+ this.node = node;
2320
+ this.offset = offset;
2321
+ this.pos = -1;
2401
2322
  }
2402
- compareRange(from, to) { addRange(from, to, this.changes); }
2403
- comparePoint(from, to) { addRange(from, to, this.changes); }
2404
- }
2405
- function findChangedDeco(a, b, diff) {
2406
- let comp = new DecorationComparator$1;
2407
- RangeSet.compare(a, b, diff, comp);
2408
- return comp.changes;
2409
- }
2410
- function inUneditable(node, inside) {
2411
- for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2412
- if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2413
- return true;
2414
- }
2415
- }
2416
- return false;
2417
2323
  }
2418
2324
 
2419
- /**
2420
- Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
2421
- */
2422
- var Direction = /*@__PURE__*/(function (Direction) {
2423
- // (These are chosen to match the base levels, in bidi algorithm
2424
- // terms, of spans in that direction.)
2425
- /**
2426
- Left-to-right.
2427
- */
2428
- Direction[Direction["LTR"] = 0] = "LTR";
2429
- /**
2430
- Right-to-left.
2431
- */
2432
- Direction[Direction["RTL"] = 1] = "RTL";
2433
- return Direction})(Direction || (Direction = {}));
2434
- const LTR = Direction.LTR, RTL = Direction.RTL;
2435
- // Decode a string with each type encoded as log2(type)
2436
- function dec(str) {
2437
- let result = [];
2438
- for (let i = 0; i < str.length; i++)
2439
- result.push(1 << +str[i]);
2440
- return result;
2441
- }
2442
- // Character types for codepoints 0 to 0xf8
2443
- const LowTypes = /*@__PURE__*/dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
2444
- // Character types for codepoints 0x600 to 0x6f9
2445
- const ArabicTypes = /*@__PURE__*/dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
2446
- const Brackets = /*@__PURE__*/Object.create(null), BracketStack = [];
2447
- // There's a lot more in
2448
- // https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
2449
- // which are left out to keep code size down.
2450
- for (let p of ["()", "[]", "{}"]) {
2451
- let l = /*@__PURE__*/p.charCodeAt(0), r = /*@__PURE__*/p.charCodeAt(1);
2452
- Brackets[l] = r;
2453
- Brackets[r] = -l;
2454
- }
2455
- function charType(ch) {
2456
- return ch <= 0xf7 ? LowTypes[ch] :
2457
- 0x590 <= ch && ch <= 0x5f4 ? 2 /* R */ :
2458
- 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
2459
- 0x6ee <= ch && ch <= 0x8ac ? 4 /* AL */ :
2460
- 0x2000 <= ch && ch <= 0x200b ? 256 /* NI */ :
2461
- ch == 0x200c ? 256 /* NI */ : 1 /* L */;
2462
- }
2463
- const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
2464
- /**
2465
- Represents a contiguous range of text that has a single direction
2466
- (as in left-to-right or right-to-left).
2467
- */
2468
- class BidiSpan {
2469
- /**
2470
- @internal
2471
- */
2472
- constructor(
2473
- /**
2474
- The start of the span (relative to the start of the line).
2475
- */
2476
- from,
2477
- /**
2478
- The end of the span.
2479
- */
2480
- to,
2481
- /**
2482
- The ["bidi
2483
- level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
2484
- of the span (in this context, 0 means
2485
- left-to-right, 1 means right-to-left, 2 means left-to-right
2486
- number inside right-to-left text).
2487
- */
2488
- level) {
2489
- this.from = from;
2490
- this.to = to;
2491
- this.level = level;
2325
+ class DocView extends ContentView {
2326
+ constructor(view) {
2327
+ super();
2328
+ this.view = view;
2329
+ this.compositionDeco = Decoration.none;
2330
+ this.decorations = [];
2331
+ // Track a minimum width for the editor. When measuring sizes in
2332
+ // measureVisibleLineHeights, this is updated to point at the width
2333
+ // of a given element and its extent in the document. When a change
2334
+ // happens in that range, these are reset. That way, once we've seen
2335
+ // a line/element of a given length, we keep the editor wide enough
2336
+ // to fit at least that element, until it is changed, at which point
2337
+ // we forget it again.
2338
+ this.minWidth = 0;
2339
+ this.minWidthFrom = 0;
2340
+ this.minWidthTo = 0;
2341
+ // Track whether the DOM selection was set in a lossy way, so that
2342
+ // we don't mess it up when reading it back it
2343
+ this.impreciseAnchor = null;
2344
+ this.impreciseHead = null;
2345
+ this.forceSelection = false;
2346
+ // Used by the resize observer to ignore resizes that we caused
2347
+ // ourselves
2348
+ this.lastUpdate = Date.now();
2349
+ this.setDOM(view.contentDOM);
2350
+ this.children = [new LineView];
2351
+ this.children[0].setParent(this);
2352
+ this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
2492
2353
  }
2493
- /**
2494
- The direction of this span.
2495
- */
2496
- get dir() { return this.level % 2 ? RTL : LTR; }
2497
- /**
2498
- @internal
2499
- */
2500
- side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
2501
- /**
2502
- @internal
2503
- */
2504
- static find(order, index, level, assoc) {
2505
- let maybe = -1;
2506
- for (let i = 0; i < order.length; i++) {
2507
- let span = order[i];
2508
- if (span.from <= index && span.to >= index) {
2509
- if (span.level == level)
2510
- return i;
2511
- // When multiple spans match, if assoc != 0, take the one that
2512
- // covers that side, otherwise take the one with the minimum
2513
- // level.
2514
- if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
2515
- maybe = i;
2354
+ get root() { return this.view.root; }
2355
+ get editorView() { return this.view; }
2356
+ get length() { return this.view.state.doc.length; }
2357
+ // Update the document view to a given state. scrollIntoView can be
2358
+ // used as a hint to compute a new viewport that includes that
2359
+ // position, if we know the editor is going to scroll that position
2360
+ // into view.
2361
+ update(update) {
2362
+ let changedRanges = update.changedRanges;
2363
+ if (this.minWidth > 0 && changedRanges.length) {
2364
+ if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
2365
+ this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
2366
+ }
2367
+ else {
2368
+ this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
2369
+ this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2516
2370
  }
2517
2371
  }
2518
- if (maybe < 0)
2519
- throw new RangeError("Index out of range");
2520
- return maybe;
2372
+ if (this.view.inputState.composing < 0)
2373
+ this.compositionDeco = Decoration.none;
2374
+ else if (update.transactions.length || this.dirty)
2375
+ this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2376
+ // When the DOM nodes around the selection are moved to another
2377
+ // parent, Chrome sometimes reports a different selection through
2378
+ // getSelection than the one that it actually shows to the user.
2379
+ // This forces a selection update when lines are joined to work
2380
+ // around that. Issue #54
2381
+ if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
2382
+ update.state.doc.lines != update.startState.doc.lines)
2383
+ this.forceSelection = true;
2384
+ let prevDeco = this.decorations, deco = this.updateDeco();
2385
+ let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
2386
+ changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
2387
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
2388
+ return false;
2389
+ }
2390
+ else {
2391
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
2392
+ if (update.transactions.length)
2393
+ this.lastUpdate = Date.now();
2394
+ return true;
2395
+ }
2521
2396
  }
2522
- }
2523
- // Reused array of character types
2524
- const types = [];
2525
- function computeOrder(line, direction) {
2526
- let len = line.length, outerType = direction == LTR ? 1 /* L */ : 2 /* R */, oppositeType = direction == LTR ? 2 /* R */ : 1 /* L */;
2527
- if (!line || outerType == 1 /* L */ && !BidiRE.test(line))
2528
- return trivialOrder(len);
2529
- // W1. Examine each non-spacing mark (NSM) in the level run, and
2530
- // change the type of the NSM to the type of the previous
2531
- // character. If the NSM is at the start of the level run, it will
2532
- // get the type of sor.
2533
- // W2. Search backwards from each instance of a European number
2534
- // until the first strong type (R, L, AL, or sor) is found. If an
2535
- // AL is found, change the type of the European number to Arabic
2536
- // number.
2537
- // W3. Change all ALs to R.
2538
- // (Left after this: L, R, EN, AN, ET, CS, NI)
2539
- for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2540
- let type = charType(line.charCodeAt(i));
2541
- if (type == 512 /* NSM */)
2542
- type = prev;
2543
- else if (type == 8 /* EN */ && prevStrong == 4 /* AL */)
2544
- type = 16 /* AN */;
2545
- types[i] = type == 4 /* AL */ ? 2 /* R */ : type;
2546
- if (type & 7 /* Strong */)
2547
- prevStrong = type;
2548
- prev = type;
2397
+ // Used by update and the constructor do perform the actual DOM
2398
+ // update
2399
+ updateInner(changes, deco, oldLength) {
2400
+ this.view.viewState.mustMeasureContent = true;
2401
+ this.updateChildren(changes, deco, oldLength);
2402
+ let { observer } = this.view;
2403
+ observer.ignore(() => {
2404
+ // Lock the height during redrawing, since Chrome sometimes
2405
+ // messes with the scroll position during DOM mutation (though
2406
+ // no relayout is triggered and I cannot imagine how it can
2407
+ // recompute the scroll position without a layout)
2408
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2409
+ this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2410
+ // Chrome will sometimes, when DOM mutations occur directly
2411
+ // around the selection, get confused and report a different
2412
+ // selection from the one it displays (issue #218). This tries
2413
+ // to detect that situation.
2414
+ let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2415
+ this.sync(track);
2416
+ this.dirty = 0 /* Not */;
2417
+ if (track && (track.written || observer.selectionRange.focusNode != track.node))
2418
+ this.forceSelection = true;
2419
+ this.dom.style.height = "";
2420
+ });
2421
+ let gaps = [];
2422
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2423
+ for (let child of this.children)
2424
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2425
+ gaps.push(child.dom);
2426
+ observer.updateGaps(gaps);
2427
+ }
2428
+ updateChildren(changes, deco, oldLength) {
2429
+ let cursor = this.childCursor(oldLength);
2430
+ for (let i = changes.length - 1;; i--) {
2431
+ let next = i >= 0 ? changes[i] : null;
2432
+ if (!next)
2433
+ break;
2434
+ let { fromA, toA, fromB, toB } = next;
2435
+ let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2436
+ let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2437
+ let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2438
+ replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2439
+ }
2440
+ }
2441
+ // Sync the DOM selection to this.state.selection
2442
+ updateSelection(mustRead = false, fromPointer = false) {
2443
+ if (mustRead)
2444
+ this.view.observer.readSelectionRange();
2445
+ if (!(fromPointer || this.mayControlSelection()) ||
2446
+ browser.ios && this.view.inputState.rapidCompositionStart)
2447
+ return;
2448
+ let force = this.forceSelection;
2449
+ this.forceSelection = false;
2450
+ let main = this.view.state.selection.main;
2451
+ // FIXME need to handle the case where the selection falls inside a block range
2452
+ let anchor = this.domAtPos(main.anchor);
2453
+ let head = main.empty ? anchor : this.domAtPos(main.head);
2454
+ // Always reset on Firefox when next to an uneditable node to
2455
+ // avoid invisible cursor bugs (#111)
2456
+ if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2457
+ let dummy = document.createTextNode("");
2458
+ this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2459
+ anchor = head = new DOMPos(dummy, 0);
2460
+ force = true;
2461
+ }
2462
+ let domSel = this.view.observer.selectionRange;
2463
+ // If the selection is already here, or in an equivalent position, don't touch it
2464
+ if (force || !domSel.focusNode ||
2465
+ !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2466
+ !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2467
+ this.view.observer.ignore(() => {
2468
+ // Chrome Android will hide the virtual keyboard when tapping
2469
+ // inside an uneditable node, and not bring it back when we
2470
+ // move the cursor to its proper position. This tries to
2471
+ // restore the keyboard by cycling focus.
2472
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
2473
+ inUneditable(domSel.focusNode, this.dom)) {
2474
+ this.dom.blur();
2475
+ this.dom.focus({ preventScroll: true });
2476
+ }
2477
+ let rawSel = getSelection(this.root);
2478
+ if (main.empty) {
2479
+ // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2480
+ if (browser.gecko) {
2481
+ let nextTo = nextToUneditable(anchor.node, anchor.offset);
2482
+ if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2483
+ let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2484
+ if (text)
2485
+ anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2486
+ }
2487
+ }
2488
+ rawSel.collapse(anchor.node, anchor.offset);
2489
+ if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2490
+ domSel.cursorBidiLevel = main.bidiLevel;
2491
+ }
2492
+ else if (rawSel.extend) {
2493
+ // Selection.extend can be used to create an 'inverted' selection
2494
+ // (one where the focus is before the anchor), but not all
2495
+ // browsers support it yet.
2496
+ rawSel.collapse(anchor.node, anchor.offset);
2497
+ rawSel.extend(head.node, head.offset);
2498
+ }
2499
+ else {
2500
+ // Primitive (IE) way
2501
+ let range = document.createRange();
2502
+ if (main.anchor > main.head)
2503
+ [anchor, head] = [head, anchor];
2504
+ range.setEnd(head.node, head.offset);
2505
+ range.setStart(anchor.node, anchor.offset);
2506
+ rawSel.removeAllRanges();
2507
+ rawSel.addRange(range);
2508
+ }
2509
+ });
2510
+ this.view.observer.setSelectionRange(anchor, head);
2511
+ }
2512
+ this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2513
+ this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2514
+ }
2515
+ enforceCursorAssoc() {
2516
+ if (this.compositionDeco.size)
2517
+ return;
2518
+ let cursor = this.view.state.selection.main;
2519
+ let sel = getSelection(this.root);
2520
+ if (!cursor.empty || !cursor.assoc || !sel.modify)
2521
+ return;
2522
+ let line = LineView.find(this, cursor.head);
2523
+ if (!line)
2524
+ return;
2525
+ let lineStart = line.posAtStart;
2526
+ if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2527
+ return;
2528
+ let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2529
+ if (!before || !after || before.bottom > after.top)
2530
+ return;
2531
+ let dom = this.domAtPos(cursor.head + cursor.assoc);
2532
+ sel.collapse(dom.node, dom.offset);
2533
+ sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2549
2534
  }
2550
- // W5. A sequence of European terminators adjacent to European
2551
- // numbers changes to all European numbers.
2552
- // W6. Otherwise, separators and terminators change to Other
2553
- // Neutral.
2554
- // W7. Search backwards from each instance of a European number
2555
- // until the first strong type (R, L, or sor) is found. If an L is
2556
- // found, then change the type of the European number to L.
2557
- // (Left after this: L, R, EN+AN, NI)
2558
- for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2559
- let type = types[i];
2560
- if (type == 128 /* CS */) {
2561
- if (i < len - 1 && prev == types[i + 1] && (prev & 24 /* Num */))
2562
- type = types[i] = prev;
2563
- else
2564
- types[i] = 256 /* NI */;
2535
+ mayControlSelection() {
2536
+ return this.view.state.facet(editable) ? this.root.activeElement == this.dom
2537
+ : hasSelection(this.dom, this.view.observer.selectionRange);
2538
+ }
2539
+ nearest(dom) {
2540
+ for (let cur = dom; cur;) {
2541
+ let domView = ContentView.get(cur);
2542
+ if (domView && domView.rootView == this)
2543
+ return domView;
2544
+ cur = cur.parentNode;
2565
2545
  }
2566
- else if (type == 64 /* ET */) {
2567
- let end = i + 1;
2568
- while (end < len && types[end] == 64 /* ET */)
2569
- end++;
2570
- let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2571
- for (let j = i; j < end; j++)
2572
- types[j] = replace;
2573
- i = end - 1;
2546
+ return null;
2547
+ }
2548
+ posFromDOM(node, offset) {
2549
+ let view = this.nearest(node);
2550
+ if (!view)
2551
+ throw new RangeError("Trying to find position for a DOM position outside of the document");
2552
+ return view.localPosFromDOM(node, offset) + view.posAtStart;
2553
+ }
2554
+ domAtPos(pos) {
2555
+ let { i, off } = this.childCursor().findPos(pos, -1);
2556
+ for (; i < this.children.length - 1;) {
2557
+ let child = this.children[i];
2558
+ if (off < child.length || child instanceof LineView)
2559
+ break;
2560
+ i++;
2561
+ off = 0;
2574
2562
  }
2575
- else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2576
- types[i] = 1 /* L */;
2563
+ return this.children[i].domAtPos(off);
2564
+ }
2565
+ coordsAt(pos, side) {
2566
+ for (let off = this.length, i = this.children.length - 1;; i--) {
2567
+ let child = this.children[i], start = off - child.breakAfter - child.length;
2568
+ if (pos > start ||
2569
+ (pos == start && child.type != BlockType.WidgetBefore && child.type != BlockType.WidgetAfter &&
2570
+ (!i || side == 2 || this.children[i - 1].breakAfter ||
2571
+ (this.children[i - 1].type == BlockType.WidgetBefore && side > -2))))
2572
+ return child.coordsAt(pos - start, side);
2573
+ off = start;
2577
2574
  }
2578
- prev = type;
2579
- if (type & 7 /* Strong */)
2580
- prevStrong = type;
2581
2575
  }
2582
- // N0. Process bracket pairs in an isolating run sequence
2583
- // sequentially in the logical order of the text positions of the
2584
- // opening paired brackets using the logic given below. Within this
2585
- // scope, bidirectional types EN and AN are treated as R.
2586
- for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2587
- // Keeps [startIndex, type, strongSeen] triples for each open
2588
- // bracket on BracketStack.
2589
- if (br = Brackets[ch = line.charCodeAt(i)]) {
2590
- if (br < 0) { // Closing bracket
2591
- for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2592
- if (BracketStack[sJ + 1] == -br) {
2593
- let flags = BracketStack[sJ + 2];
2594
- let type = (flags & 2 /* EmbedInside */) ? outerType :
2595
- !(flags & 4 /* OppositeInside */) ? 0 :
2596
- (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2597
- if (type)
2598
- types[i] = types[BracketStack[sJ]] = type;
2599
- sI = sJ;
2600
- break;
2576
+ measureVisibleLineHeights() {
2577
+ let result = [], { from, to } = this.view.viewState.viewport;
2578
+ let contentWidth = this.view.contentDOM.clientWidth;
2579
+ let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2580
+ let widest = -1;
2581
+ for (let pos = 0, i = 0; i < this.children.length; i++) {
2582
+ let child = this.children[i], end = pos + child.length;
2583
+ if (end > to)
2584
+ break;
2585
+ if (pos >= from) {
2586
+ let childRect = child.dom.getBoundingClientRect();
2587
+ result.push(childRect.height);
2588
+ if (isWider) {
2589
+ let last = child.dom.lastChild;
2590
+ let rects = last ? clientRectsFor(last) : [];
2591
+ if (rects.length) {
2592
+ let rect = rects[rects.length - 1];
2593
+ let width = this.view.textDirection == Direction.LTR ? rect.right - childRect.left
2594
+ : childRect.right - rect.left;
2595
+ if (width > widest) {
2596
+ widest = width;
2597
+ this.minWidth = contentWidth;
2598
+ this.minWidthFrom = pos;
2599
+ this.minWidthTo = end;
2600
+ }
2601
2601
  }
2602
2602
  }
2603
2603
  }
2604
- else if (BracketStack.length == 189 /* MaxDepth */) {
2605
- break;
2606
- }
2607
- else {
2608
- BracketStack[sI++] = i;
2609
- BracketStack[sI++] = ch;
2610
- BracketStack[sI++] = context;
2611
- }
2604
+ pos = end + child.breakAfter;
2612
2605
  }
2613
- else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2614
- let embed = type == outerType;
2615
- context = embed ? 0 : 1 /* OppositeBefore */;
2616
- for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2617
- let cur = BracketStack[sJ + 2];
2618
- if (cur & 2 /* EmbedInside */)
2619
- break;
2620
- if (embed) {
2621
- BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2622
- }
2623
- else {
2624
- if (cur & 4 /* OppositeInside */)
2625
- break;
2626
- BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2627
- }
2606
+ return result;
2607
+ }
2608
+ measureTextSize() {
2609
+ for (let child of this.children) {
2610
+ if (child instanceof LineView) {
2611
+ let measure = child.measureTextSize();
2612
+ if (measure)
2613
+ return measure;
2628
2614
  }
2629
2615
  }
2616
+ // If no workable line exists, force a layout of a measurable element
2617
+ let dummy = document.createElement("div"), lineHeight, charWidth;
2618
+ dummy.className = "cm-line";
2619
+ dummy.textContent = "abc def ghi jkl mno pqr stu";
2620
+ this.view.observer.ignore(() => {
2621
+ this.dom.appendChild(dummy);
2622
+ let rect = clientRectsFor(dummy.firstChild)[0];
2623
+ lineHeight = dummy.getBoundingClientRect().height;
2624
+ charWidth = rect ? rect.width / 27 : 7;
2625
+ dummy.remove();
2626
+ });
2627
+ return { lineHeight, charWidth };
2630
2628
  }
2631
- // N1. A sequence of neutrals takes the direction of the
2632
- // surrounding strong text if the text on both sides has the same
2633
- // direction. European and Arabic numbers act as if they were R in
2634
- // terms of their influence on neutrals. Start-of-level-run (sor)
2635
- // and end-of-level-run (eor) are used at level run boundaries.
2636
- // N2. Any remaining neutrals take the embedding direction.
2637
- // (Left after this: L, R, EN+AN)
2638
- for (let i = 0; i < len; i++) {
2639
- if (types[i] == 256 /* NI */) {
2640
- let end = i + 1;
2641
- while (end < len && types[end] == 256 /* NI */)
2642
- end++;
2643
- let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2644
- let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2645
- let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2646
- for (let j = i; j < end; j++)
2647
- types[j] = replace;
2648
- i = end - 1;
2649
- }
2629
+ childCursor(pos = this.length) {
2630
+ // Move back to start of last element when possible, so that
2631
+ // `ChildCursor.findPos` doesn't have to deal with the edge case
2632
+ // of being after the last element.
2633
+ let i = this.children.length;
2634
+ if (i)
2635
+ pos -= this.children[--i].length;
2636
+ return new ChildCursor(this.children, pos, i);
2650
2637
  }
2651
- // Here we depart from the documented algorithm, in order to avoid
2652
- // building up an actual levels array. Since there are only three
2653
- // levels (0, 1, 2) in an implementation that doesn't take
2654
- // explicit embedding into account, we can build up the order on
2655
- // the fly, without following the level-based algorithm.
2656
- let order = [];
2657
- if (outerType == 1 /* L */) {
2658
- for (let i = 0; i < len;) {
2659
- let start = i, rtl = types[i++] != 1 /* L */;
2660
- while (i < len && rtl == (types[i] != 1 /* L */))
2661
- i++;
2662
- if (rtl) {
2663
- for (let j = i; j > start;) {
2664
- let end = j, l = types[--j] != 2 /* R */;
2665
- while (j > start && l == (types[j - 1] != 2 /* R */))
2666
- j--;
2667
- order.push(new BidiSpan(j, end, l ? 2 : 1));
2668
- }
2669
- }
2670
- else {
2671
- order.push(new BidiSpan(start, i, 0));
2638
+ computeBlockGapDeco() {
2639
+ let deco = [], vs = this.view.viewState;
2640
+ for (let pos = 0, i = 0;; i++) {
2641
+ let next = i == vs.viewports.length ? null : vs.viewports[i];
2642
+ let end = next ? next.from - 1 : this.length;
2643
+ if (end > pos) {
2644
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2645
+ deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2672
2646
  }
2647
+ if (!next)
2648
+ break;
2649
+ pos = next.to + 1;
2673
2650
  }
2651
+ return Decoration.set(deco);
2652
+ }
2653
+ updateDeco() {
2654
+ return this.decorations = [
2655
+ ...this.view.pluginField(PluginField.decorations),
2656
+ ...this.view.state.facet(decorations),
2657
+ this.compositionDeco,
2658
+ this.computeBlockGapDeco(),
2659
+ this.view.viewState.lineGapDeco
2660
+ ];
2661
+ }
2662
+ scrollIntoView({ range, center }) {
2663
+ let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2664
+ if (!rect)
2665
+ return;
2666
+ if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2667
+ rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2668
+ right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2669
+ let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2670
+ for (let margins of this.view.pluginField(PluginField.scrollMargins))
2671
+ if (margins) {
2672
+ let { left, right, top, bottom } = margins;
2673
+ if (left != null)
2674
+ mLeft = Math.max(mLeft, left);
2675
+ if (right != null)
2676
+ mRight = Math.max(mRight, right);
2677
+ if (top != null)
2678
+ mTop = Math.max(mTop, top);
2679
+ if (bottom != null)
2680
+ mBottom = Math.max(mBottom, bottom);
2681
+ }
2682
+ scrollRectIntoView(this.view.scrollDOM, {
2683
+ left: rect.left - mLeft, top: rect.top - mTop,
2684
+ right: rect.right + mRight, bottom: rect.bottom + mBottom
2685
+ }, range.head < range.anchor ? -1 : 1, center);
2686
+ }
2687
+ }
2688
+ function betweenUneditable(pos) {
2689
+ return pos.node.nodeType == 1 && pos.node.firstChild &&
2690
+ (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2691
+ (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2692
+ }
2693
+ class BlockGapWidget extends WidgetType {
2694
+ constructor(height) {
2695
+ super();
2696
+ this.height = height;
2697
+ }
2698
+ toDOM() {
2699
+ let elt = document.createElement("div");
2700
+ this.updateDOM(elt);
2701
+ return elt;
2702
+ }
2703
+ eq(other) { return other.height == this.height; }
2704
+ updateDOM(elt) {
2705
+ elt.style.height = this.height + "px";
2706
+ return true;
2707
+ }
2708
+ get estimatedHeight() { return this.height; }
2709
+ }
2710
+ function computeCompositionDeco(view, changes) {
2711
+ let sel = view.observer.selectionRange;
2712
+ let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2713
+ if (!textNode)
2714
+ return Decoration.none;
2715
+ let cView = view.docView.nearest(textNode);
2716
+ if (!cView)
2717
+ return Decoration.none;
2718
+ let from, to, topNode = textNode;
2719
+ if (cView instanceof LineView) {
2720
+ while (topNode.parentNode != cView.dom)
2721
+ topNode = topNode.parentNode;
2722
+ let prev = topNode.previousSibling;
2723
+ while (prev && !ContentView.get(prev))
2724
+ prev = prev.previousSibling;
2725
+ from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2674
2726
  }
2675
2727
  else {
2676
- for (let i = 0; i < len;) {
2677
- let start = i, rtl = types[i++] == 2 /* R */;
2678
- while (i < len && rtl == (types[i] == 2 /* R */))
2679
- i++;
2680
- order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2728
+ for (;;) {
2729
+ let { parent } = cView;
2730
+ if (!parent)
2731
+ return Decoration.none;
2732
+ if (parent instanceof LineView)
2733
+ break;
2734
+ cView = parent;
2681
2735
  }
2736
+ from = cView.posAtStart;
2737
+ to = from + cView.length;
2738
+ topNode = cView.dom;
2682
2739
  }
2683
- return order;
2740
+ let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2741
+ let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2742
+ new DOMReader([], view).readRange(topNode.firstChild, null).text;
2743
+ if (newTo - newFrom < text.length) {
2744
+ if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2745
+ newTo = newFrom + text.length;
2746
+ else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2747
+ newFrom = newTo - text.length;
2748
+ else
2749
+ return Decoration.none;
2750
+ }
2751
+ else if (state.sliceDoc(newFrom, newTo) != text) {
2752
+ return Decoration.none;
2753
+ }
2754
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2684
2755
  }
2685
- function trivialOrder(length) {
2686
- return [new BidiSpan(0, length, 0)];
2756
+ class CompositionWidget extends WidgetType {
2757
+ constructor(top, text) {
2758
+ super();
2759
+ this.top = top;
2760
+ this.text = text;
2761
+ }
2762
+ eq(other) { return this.top == other.top && this.text == other.text; }
2763
+ toDOM() { return this.top; }
2764
+ ignoreEvent() { return false; }
2765
+ get customView() { return CompositionView; }
2687
2766
  }
2688
- let movedOver = "";
2689
- function moveVisually(line, order, dir, start, forward) {
2690
- var _a;
2691
- let startIndex = start.head - line.from, spanI = -1;
2692
- if (startIndex == 0) {
2693
- if (!forward || !line.length)
2694
- return null;
2695
- if (order[0].level != dir) {
2696
- startIndex = order[0].side(false, dir);
2697
- spanI = 0;
2767
+ function nearbyTextNode(node, offset, side) {
2768
+ for (;;) {
2769
+ if (node.nodeType == 3)
2770
+ return node;
2771
+ if (node.nodeType == 1 && offset > 0 && side <= 0) {
2772
+ node = node.childNodes[offset - 1];
2773
+ offset = maxOffset(node);
2698
2774
  }
2699
- }
2700
- else if (startIndex == line.length) {
2701
- if (forward)
2775
+ else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2776
+ node = node.childNodes[offset];
2777
+ offset = 0;
2778
+ }
2779
+ else {
2702
2780
  return null;
2703
- let last = order[order.length - 1];
2704
- if (last.level != dir) {
2705
- startIndex = last.side(true, dir);
2706
- spanI = order.length - 1;
2707
2781
  }
2708
2782
  }
2709
- if (spanI < 0)
2710
- spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2711
- let span = order[spanI];
2712
- // End of span. (But not end of line--that was checked for above.)
2713
- if (startIndex == span.side(forward, dir)) {
2714
- span = order[spanI += forward ? 1 : -1];
2715
- startIndex = span.side(!forward, dir);
2783
+ }
2784
+ function nextToUneditable(node, offset) {
2785
+ if (node.nodeType != 1)
2786
+ return 0;
2787
+ return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2788
+ (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2789
+ }
2790
+ class DecorationComparator$1 {
2791
+ constructor() {
2792
+ this.changes = [];
2716
2793
  }
2717
- let indexForward = forward == (span.dir == dir);
2718
- let nextIndex = findClusterBreak(line.text, startIndex, indexForward);
2719
- movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2720
- if (nextIndex != span.side(forward, dir))
2721
- return EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2722
- let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2723
- if (!nextSpan && span.level != dir)
2724
- return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2725
- if (nextSpan && nextSpan.level < span.level)
2726
- return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2727
- return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2794
+ compareRange(from, to) { addRange(from, to, this.changes); }
2795
+ comparePoint(from, to) { addRange(from, to, this.changes); }
2796
+ }
2797
+ function findChangedDeco(a, b, diff) {
2798
+ let comp = new DecorationComparator$1;
2799
+ RangeSet.compare(a, b, diff, comp);
2800
+ return comp.changes;
2801
+ }
2802
+ function inUneditable(node, inside) {
2803
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2804
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2805
+ return true;
2806
+ }
2807
+ }
2808
+ return false;
2728
2809
  }
2729
2810
 
2730
2811
  function groupAt(state, pos, bias = 1) {
@@ -2862,21 +2943,29 @@ function domPosInText(node, x, y) {
2862
2943
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2863
2944
  var _a;
2864
2945
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2865
- let halfLine = view.defaultLineHeight / 2;
2866
- let block, yOffset = y - docTop;
2867
- for (let bounced = false;;) {
2946
+ let block, yOffset = y - docTop, { docHeight } = view.viewState;
2947
+ if (yOffset < 0 || yOffset > docHeight) {
2948
+ if (precise)
2949
+ return null;
2950
+ yOffset = yOffset < 0 ? 0 : docHeight;
2951
+ }
2952
+ // Scan for a text block near the queried y position
2953
+ for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
2868
2954
  block = view.elementAtHeight(yOffset);
2869
- if (block.top > yOffset || block.bottom < yOffset) {
2870
- bias = block.top > yOffset ? -1 : 1;
2871
- yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2955
+ if (block.type == BlockType.Text)
2956
+ break;
2957
+ for (;;) {
2958
+ // Move the y position out of this block
2959
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2960
+ if (yOffset >= 0 && yOffset <= docHeight)
2961
+ break;
2962
+ // If the document consists entirely of replaced widgets, we
2963
+ // won't find a text block, so return 0
2872
2964
  if (bounced)
2873
2965
  return precise ? null : 0;
2874
- else
2875
- bounced = true;
2966
+ bounced = true;
2967
+ bias = -bias;
2876
2968
  }
2877
- if (block.type == BlockType.Text)
2878
- break;
2879
- yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2880
2969
  }
2881
2970
  y = docTop + yOffset;
2882
2971
  let lineStart = block.from;
@@ -3034,14 +3123,6 @@ class InputState {
3034
3123
  constructor(view) {
3035
3124
  this.lastKeyCode = 0;
3036
3125
  this.lastKeyTime = 0;
3037
- // On Chrome Android, backspace near widgets is just completely
3038
- // broken, and there are no key events, so we need to handle the
3039
- // beforeinput event. Deleting stuff will often create a flurry of
3040
- // events, and interrupting it before it is done just makes
3041
- // subsequent events even more broken, so again, we hold off doing
3042
- // anything until the browser is finished with whatever it is trying
3043
- // to do.
3044
- this.pendingAndroidKey = undefined;
3045
3126
  // On iOS, some keys need to have their default behavior happen
3046
3127
  // (after which we retroactively handle them and reset the DOM) to
3047
3128
  // avoid messing up the virtual keyboard state.
@@ -3110,22 +3191,15 @@ class InputState {
3110
3191
  }
3111
3192
  runCustomHandlers(type, view, event) {
3112
3193
  for (let set of this.customHandlers) {
3113
- let handler = set.handlers[type], handled = false;
3194
+ let handler = set.handlers[type];
3114
3195
  if (handler) {
3115
3196
  try {
3116
- handled = handler.call(set.plugin, event, view);
3197
+ if (handler.call(set.plugin, event, view))
3198
+ return true;
3117
3199
  }
3118
3200
  catch (e) {
3119
3201
  logException(view.state, e);
3120
3202
  }
3121
- if (handled || event.defaultPrevented) {
3122
- // Chrome for Android often applies a bunch of nonsensical
3123
- // DOM changes after an enter press, even when
3124
- // preventDefault-ed. This tries to ignore those.
3125
- if (browser.android && type == "keydown" && event.keyCode == 13)
3126
- view.observer.flushSoon();
3127
- return true;
3128
- }
3129
3203
  }
3130
3204
  }
3131
3205
  return false;
@@ -3149,6 +3223,16 @@ class InputState {
3149
3223
  this.lastKeyTime = Date.now();
3150
3224
  if (this.screenKeyEvent(view, event))
3151
3225
  return true;
3226
+ // Chrome for Android usually doesn't fire proper key events, but
3227
+ // occasionally does, usually surrounded by a bunch of complicated
3228
+ // composition changes. When an enter or backspace key event is
3229
+ // seen, hold off on handling DOM events for a bit, and then
3230
+ // dispatch it.
3231
+ if (browser.android && browser.chrome && !event.synthetic &&
3232
+ (event.keyCode == 13 || event.keyCode == 8)) {
3233
+ view.observer.delayAndroidKey(event.key, event.keyCode);
3234
+ return true;
3235
+ }
3152
3236
  // Prevent the default behavior of Enter on iOS makes the
3153
3237
  // virtual keyboard get stuck in the wrong (lowercase)
3154
3238
  // state. So we let it go through, and then, in
@@ -3170,24 +3254,6 @@ class InputState {
3170
3254
  this.pendingIOSKey = undefined;
3171
3255
  return dispatchKey(view.contentDOM, key.key, key.keyCode);
3172
3256
  }
3173
- // This causes the DOM observer to pause for a bit, and sets an
3174
- // animation frame (which seems the most reliable way to detect
3175
- // 'Chrome is done flailing about messing with the DOM') to fire a
3176
- // fake key event and re-sync the view again.
3177
- setPendingAndroidKey(view, pending) {
3178
- this.pendingAndroidKey = pending;
3179
- requestAnimationFrame(() => {
3180
- let key = this.pendingAndroidKey;
3181
- if (!key)
3182
- return;
3183
- this.pendingAndroidKey = undefined;
3184
- view.observer.processRecords();
3185
- let startState = view.state;
3186
- dispatchKey(view.contentDOM, key.key, key.keyCode);
3187
- if (view.state == startState)
3188
- view.docView.reset(true);
3189
- });
3190
- }
3191
3257
  ignoreDuringComposition(event) {
3192
3258
  if (!/^key/.test(event.type))
3193
3259
  return false;
@@ -3217,10 +3283,10 @@ class InputState {
3217
3283
  return (event.type == "keydown" && event.keyCode != 229) ||
3218
3284
  event.type == "compositionend" && !browser.ios;
3219
3285
  }
3220
- startMouseSelection(view, event, style) {
3286
+ startMouseSelection(mouseSelection) {
3221
3287
  if (this.mouseSelection)
3222
3288
  this.mouseSelection.destroy();
3223
- this.mouseSelection = new MouseSelection(this, view, event, style);
3289
+ this.mouseSelection = mouseSelection;
3224
3290
  }
3225
3291
  update(update) {
3226
3292
  if (this.mouseSelection)
@@ -3241,10 +3307,10 @@ const PendingKeys = [
3241
3307
  // Key codes for modifier keys
3242
3308
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3243
3309
  class MouseSelection {
3244
- constructor(inputState, view, startEvent, style) {
3245
- this.inputState = inputState;
3310
+ constructor(view, startEvent, style, mustSelect) {
3246
3311
  this.view = view;
3247
3312
  this.style = style;
3313
+ this.mustSelect = mustSelect;
3248
3314
  this.lastEvent = startEvent;
3249
3315
  let doc = view.contentDOM.ownerDocument;
3250
3316
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
@@ -3278,16 +3344,18 @@ class MouseSelection {
3278
3344
  let doc = this.view.contentDOM.ownerDocument;
3279
3345
  doc.removeEventListener("mousemove", this.move);
3280
3346
  doc.removeEventListener("mouseup", this.up);
3281
- this.inputState.mouseSelection = null;
3347
+ this.view.inputState.mouseSelection = null;
3282
3348
  }
3283
3349
  select(event) {
3284
3350
  let selection = this.style.get(event, this.extend, this.multiple);
3285
- if (!selection.eq(this.view.state.selection) || selection.main.assoc != this.view.state.selection.main.assoc)
3351
+ if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3352
+ selection.main.assoc != this.view.state.selection.main.assoc)
3286
3353
  this.view.dispatch({
3287
3354
  selection,
3288
3355
  userEvent: "select.pointer",
3289
3356
  scrollIntoView: true
3290
3357
  });
3358
+ this.mustSelect = false;
3291
3359
  }
3292
3360
  update(update) {
3293
3361
  if (update.docChanged && this.dragging)
@@ -3406,9 +3474,10 @@ handlers.mousedown = (view, event) => {
3406
3474
  if (!style && event.button == 0)
3407
3475
  style = basicMouseSelection(view, event);
3408
3476
  if (style) {
3409
- if (view.root.activeElement != view.contentDOM)
3477
+ let mustFocus = view.root.activeElement != view.contentDOM;
3478
+ if (mustFocus)
3410
3479
  view.observer.ignore(() => focusPreventScroll(view.contentDOM));
3411
- view.inputState.startMouseSelection(view, event, style);
3480
+ view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
3412
3481
  }
3413
3482
  };
3414
3483
  function rangeForClick(view, pos, bias, type) {
@@ -3663,12 +3732,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
3663
3732
  if (view.inputState.compositionFirstChange == null)
3664
3733
  view.inputState.compositionFirstChange = true;
3665
3734
  if (view.inputState.composing < 0) {
3735
+ // FIXME possibly set a timeout to clear it again on Android
3736
+ view.inputState.composing = 0;
3666
3737
  if (view.docView.compositionDeco.size) {
3667
3738
  view.observer.flush();
3668
3739
  forceClearComposition(view, true);
3669
3740
  }
3670
- // FIXME possibly set a timeout to clear it again on Android
3671
- view.inputState.composing = 0;
3672
3741
  }
3673
3742
  };
3674
3743
  handlers.compositionend = view => {
@@ -3694,7 +3763,7 @@ handlers.beforeinput = (view, event) => {
3694
3763
  // seems to do nothing at all on Chrome).
3695
3764
  let pending;
3696
3765
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3697
- view.inputState.setPendingAndroidKey(view, pending);
3766
+ view.observer.delayAndroidKey(pending.key, pending.keyCode);
3698
3767
  if (pending.key == "Backspace" || pending.key == "Delete") {
3699
3768
  let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3700
3769
  setTimeout(() => {
@@ -4123,12 +4192,12 @@ class HeightMapBranch extends HeightMap {
4123
4192
  get break() { return this.flags & 1 /* Break */; }
4124
4193
  blockAt(height, doc, top, offset) {
4125
4194
  let mid = top + this.left.height;
4126
- return height < mid || this.right.height == 0 ? this.left.blockAt(height, doc, top, offset)
4195
+ return height < mid ? this.left.blockAt(height, doc, top, offset)
4127
4196
  : this.right.blockAt(height, doc, mid, offset + this.left.length + this.break);
4128
4197
  }
4129
4198
  lineAt(value, type, doc, top, offset) {
4130
4199
  let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
4131
- let left = type == QueryType.ByHeight ? value < rightTop || this.right.height == 0 : value < rightOffset;
4200
+ let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
4132
4201
  let base = left ? this.left.lineAt(value, type, doc, top, offset)
4133
4202
  : this.right.lineAt(value, type, doc, rightTop, rightOffset);
4134
4203
  if (this.break || (left ? base.to < rightOffset : base.from > rightOffset))
@@ -4269,7 +4338,9 @@ class NodeBuilder {
4269
4338
  }
4270
4339
  point(from, to, deco) {
4271
4340
  if (from < to || deco.heightRelevant) {
4272
- let height = deco.widget ? Math.max(0, deco.widget.estimatedHeight) : 0;
4341
+ let height = deco.widget ? deco.widget.estimatedHeight : 0;
4342
+ if (height < 0)
4343
+ height = this.oracle.lineHeight;
4273
4344
  let len = to - from;
4274
4345
  if (deco.block) {
4275
4346
  this.addBlock(new HeightMapBlock(len, height, deco.type));
@@ -4397,8 +4468,8 @@ function visiblePixelRange(dom, paddingTop) {
4397
4468
  break;
4398
4469
  }
4399
4470
  }
4400
- return { left: left - rect.left, right: right - rect.left,
4401
- top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
4471
+ return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4472
+ top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4402
4473
  }
4403
4474
  // Line gaps are placeholder widgets used to hide pieces of overlong
4404
4475
  // lines within the viewport, as a kludge to keep the editor
@@ -4625,7 +4696,7 @@ class ViewState {
4625
4696
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4626
4697
  // If scrollTarget is given, make sure the viewport includes that position
4627
4698
  if (scrollTarget) {
4628
- let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4699
+ let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4629
4700
  if (head < viewport.from || head > viewport.to) {
4630
4701
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4631
4702
  if (scrollTarget.center)
@@ -4646,6 +4717,8 @@ class ViewState {
4646
4717
  // Checks if a given viewport covers the visible part of the
4647
4718
  // document and not too much beyond that.
4648
4719
  viewportIsAppropriate({ from, to }, bias = 0) {
4720
+ if (!this.inView)
4721
+ return true;
4649
4722
  let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4650
4723
  let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4651
4724
  let { visibleTop, visibleBottom } = this;
@@ -4752,8 +4825,11 @@ class ViewState {
4752
4825
  elementAtHeight(height) {
4753
4826
  return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4754
4827
  }
4828
+ get docHeight() {
4829
+ return this.scaler.toDOM(this.heightMap.height);
4830
+ }
4755
4831
  get contentHeight() {
4756
- return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4832
+ return this.docHeight + this.paddingTop + this.paddingBottom;
4757
4833
  }
4758
4834
  }
4759
4835
  class Viewport {
@@ -5007,7 +5083,8 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5007
5083
  },
5008
5084
  ".cm-placeholder": {
5009
5085
  color: "#888",
5010
- display: "inline-block"
5086
+ display: "inline-block",
5087
+ verticalAlign: "top",
5011
5088
  },
5012
5089
  ".cm-button": {
5013
5090
  verticalAlign: "middle",
@@ -5074,6 +5151,7 @@ class DOMObserver {
5074
5151
  this.delayedFlush = -1;
5075
5152
  this.resizeTimeout = -1;
5076
5153
  this.queue = [];
5154
+ this.delayedAndroidKey = null;
5077
5155
  this.scrollTargets = [];
5078
5156
  this.intersection = null;
5079
5157
  this.resize = null;
@@ -5157,7 +5235,7 @@ class DOMObserver {
5157
5235
  }
5158
5236
  }
5159
5237
  onSelectionChange(event) {
5160
- if (!this.readSelectionRange())
5238
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5161
5239
  return;
5162
5240
  let { view } = this, sel = this.selectionRange;
5163
5241
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5253,6 +5331,32 @@ class DOMObserver {
5253
5331
  this.queue.length = 0;
5254
5332
  this.selectionChanged = false;
5255
5333
  }
5334
+ // Chrome Android, especially in combination with GBoard, not only
5335
+ // doesn't reliably fire regular key events, but also often
5336
+ // surrounds the effect of enter or backspace with a bunch of
5337
+ // composition events that, when interrupted, cause text duplication
5338
+ // or other kinds of corruption. This hack makes the editor back off
5339
+ // from handling DOM changes for a moment when such a key is
5340
+ // detected (via beforeinput or keydown), and then dispatches the
5341
+ // key event, throwing away the DOM changes if it gets handled.
5342
+ delayAndroidKey(key, keyCode) {
5343
+ if (!this.delayedAndroidKey)
5344
+ requestAnimationFrame(() => {
5345
+ let key = this.delayedAndroidKey;
5346
+ this.delayedAndroidKey = null;
5347
+ let startState = this.view.state;
5348
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5349
+ this.processRecords();
5350
+ else
5351
+ this.flush();
5352
+ if (this.view.state == startState)
5353
+ this.view.update([]);
5354
+ });
5355
+ // Since backspace beforeinput is sometimes signalled spuriously,
5356
+ // Enter always takes precedence.
5357
+ if (!this.delayedAndroidKey || key == "Enter")
5358
+ this.delayedAndroidKey = { key, keyCode };
5359
+ }
5256
5360
  flushSoon() {
5257
5361
  if (this.delayedFlush < 0)
5258
5362
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5289,13 +5393,13 @@ class DOMObserver {
5289
5393
  }
5290
5394
  // Apply pending changes, if any
5291
5395
  flush(readSelection = true) {
5292
- if (readSelection)
5293
- this.readSelectionRange();
5294
5396
  // Completely hold off flushing when pending keys are set—the code
5295
5397
  // managing those will make sure processRecords is called and the
5296
5398
  // view is resynchronized after
5297
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5399
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5298
5400
  return;
5401
+ if (readSelection)
5402
+ this.readSelectionRange();
5299
5403
  let { from, to, typeOver } = this.processRecords();
5300
5404
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5301
5405
  if (from < 0 && !newSel)
@@ -5305,7 +5409,7 @@ class DOMObserver {
5305
5409
  this.onChange(from, to, typeOver);
5306
5410
  // The view wasn't updated
5307
5411
  if (this.view.state == startState)
5308
- this.view.docView.reset(newSel);
5412
+ this.view.update([]);
5309
5413
  }
5310
5414
  readMutation(rec) {
5311
5415
  let cView = this.view.docView.nearest(rec.target);
@@ -5436,11 +5540,22 @@ function applyDOMChange(view, start, end, typeOver) {
5436
5540
  };
5437
5541
  if (change) {
5438
5542
  let startState = view.state;
5543
+ if (browser.ios && view.inputState.flushIOSKey(view))
5544
+ return;
5439
5545
  // Android browsers don't fire reasonable key events for enter,
5440
5546
  // backspace, or delete. So this detects changes that look like
5441
5547
  // they're caused by those keys, and reinterprets them as key
5442
- // events.
5443
- if (browser.ios && view.inputState.flushIOSKey(view))
5548
+ // events. (Some of these keys are also handled by beforeinput
5549
+ // events and the pendingAndroidKey mechanism, but that's not
5550
+ // reliable in all situations.)
5551
+ if (browser.android &&
5552
+ ((change.from == sel.from && change.to == sel.to &&
5553
+ change.insert.length == 1 && change.insert.lines == 2 &&
5554
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
5555
+ (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5556
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
5557
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5558
+ dispatchKey(view.contentDOM, "Delete", 46))))
5444
5559
  return;
5445
5560
  let text = change.insert.toString();
5446
5561
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
@@ -5513,76 +5628,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
5513
5628
  }
5514
5629
  return { from, toA, toB };
5515
5630
  }
5516
- class DOMReader {
5517
- constructor(points, view) {
5518
- this.points = points;
5519
- this.view = view;
5520
- this.text = "";
5521
- this.lineBreak = view.state.lineBreak;
5522
- }
5523
- readRange(start, end) {
5524
- if (!start)
5525
- return;
5526
- let parent = start.parentNode;
5527
- for (let cur = start;;) {
5528
- this.findPointBefore(parent, cur);
5529
- this.readNode(cur);
5530
- let next = cur.nextSibling;
5531
- if (next == end)
5532
- break;
5533
- let view = ContentView.get(cur), nextView = ContentView.get(next);
5534
- if (view && nextView ? view.breakAfter :
5535
- (view ? view.breakAfter : isBlockElement(cur)) ||
5536
- (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5537
- this.text += this.lineBreak;
5538
- cur = next;
5539
- }
5540
- this.findPointBefore(parent, end);
5541
- }
5542
- readNode(node) {
5543
- if (node.cmIgnore)
5544
- return;
5545
- let view = ContentView.get(node);
5546
- let fromView = view && view.overrideDOMText;
5547
- let text;
5548
- if (fromView != null)
5549
- text = fromView.sliceString(0, undefined, this.lineBreak);
5550
- else if (node.nodeType == 3)
5551
- text = node.nodeValue;
5552
- else if (node.nodeName == "BR")
5553
- text = node.nextSibling ? this.lineBreak : "";
5554
- else if (node.nodeType == 1)
5555
- this.readRange(node.firstChild, null);
5556
- if (text != null) {
5557
- this.findPointIn(node, text.length);
5558
- this.text += text;
5559
- // Chrome inserts two newlines when pressing shift-enter at the
5560
- // end of a line. This drops one of those.
5561
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
5562
- this.text = this.text.slice(0, -1);
5563
- }
5564
- }
5565
- findPointBefore(node, next) {
5566
- for (let point of this.points)
5567
- if (point.node == node && node.childNodes[point.offset] == next)
5568
- point.pos = this.text.length;
5569
- }
5570
- findPointIn(node, maxLen) {
5571
- for (let point of this.points)
5572
- if (point.node == node)
5573
- point.pos = this.text.length + Math.min(point.offset, maxLen);
5574
- }
5575
- }
5576
- function isBlockElement(node) {
5577
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
5578
- }
5579
- class DOMPoint {
5580
- constructor(node, offset) {
5581
- this.node = node;
5582
- this.offset = offset;
5583
- this.pos = -1;
5584
- }
5585
- }
5586
5631
  function selectionPoints(view) {
5587
5632
  let result = [];
5588
5633
  if (view.root.activeElement != view.contentDOM)
@@ -5665,7 +5710,9 @@ class EditorView {
5665
5710
  this.dispatch = this.dispatch.bind(this);
5666
5711
  this.root = (config.root || getRoot(config.parent) || document);
5667
5712
  this.viewState = new ViewState(config.state || EditorState.create());
5668
- this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5713
+ this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
5714
+ for (let plugin of this.plugins)
5715
+ plugin.update(this);
5669
5716
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
5670
5717
  applyDOMChange(this, from, to, typeOver);
5671
5718
  }, event => {
@@ -5802,8 +5849,10 @@ class EditorView {
5802
5849
  for (let plugin of this.plugins)
5803
5850
  plugin.destroy(this);
5804
5851
  this.viewState = new ViewState(newState);
5805
- this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5852
+ this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec));
5806
5853
  this.pluginMap.clear();
5854
+ for (let plugin of this.plugins)
5855
+ plugin.update(this);
5807
5856
  this.docView = new DocView(this);
5808
5857
  this.inputState.ensureHandlers(this);
5809
5858
  this.mountStyles();
@@ -5864,7 +5913,9 @@ class EditorView {
5864
5913
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5865
5914
  break;
5866
5915
  if (i > 5) {
5867
- console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5916
+ console.warn(this.measureRequests.length
5917
+ ? "Measure loop restarted more than 5 times"
5918
+ : "Viewport failed to stabilize");
5868
5919
  break;
5869
5920
  }
5870
5921
  let measuring = [];
@@ -5910,7 +5961,8 @@ class EditorView {
5910
5961
  }
5911
5962
  if (redrawn)
5912
5963
  this.docView.updateSelection(true);
5913
- if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5964
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
5965
+ this.measureRequests.length == 0)
5914
5966
  break;
5915
5967
  }
5916
5968
  }
@@ -6202,6 +6254,11 @@ class EditorView {
6202
6254
  Find the DOM parent node and offset (child offset if `node` is
6203
6255
  an element, character offset when it is a text node) at the
6204
6256
  given document position.
6257
+
6258
+ Note that for positions that aren't currently in
6259
+ `visibleRanges`, the resulting DOM position isn't necessarily
6260
+ meaningful (it may just point before or after a placeholder
6261
+ element).
6205
6262
  */
6206
6263
  domAtPos(pos) {
6207
6264
  return this.docView.domAtPos(pos);
@@ -6862,7 +6919,7 @@ function measureRange(view, range) {
6862
6919
  let between = [];
6863
6920
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6864
6921
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6865
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == BlockType.Text)
6922
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
6866
6923
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6867
6924
  return pieces(top).concat(between).concat(pieces(bottom));
6868
6925
  }
@@ -6935,6 +6992,22 @@ function iterMatches(doc, re, from, to, f) {
6935
6992
  f(pos + m.index, pos + m.index + m[0].length, m);
6936
6993
  }
6937
6994
  }
6995
+ function matchRanges(view, maxLength) {
6996
+ let visible = view.visibleRanges;
6997
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
6998
+ visible[0].to == view.viewport.to)
6999
+ return visible;
7000
+ let result = [];
7001
+ for (let { from, to } of visible) {
7002
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7003
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7004
+ if (result.length && result[result.length - 1].to >= from)
7005
+ result[result.length - 1].to = to;
7006
+ else
7007
+ result.push({ from, to });
7008
+ }
7009
+ return result;
7010
+ }
6938
7011
  /**
6939
7012
  Helper class used to make it easier to maintain decorations on
6940
7013
  visible code that matches a given regular expression. To be used
@@ -6946,12 +7019,13 @@ class MatchDecorator {
6946
7019
  Create a decorator.
6947
7020
  */
6948
7021
  constructor(config) {
6949
- let { regexp, decoration, boundary } = config;
7022
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
6950
7023
  if (!regexp.global)
6951
7024
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
6952
7025
  this.regexp = regexp;
6953
7026
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
6954
7027
  this.boundary = boundary;
7028
+ this.maxLength = maxLength;
6955
7029
  }
6956
7030
  /**
6957
7031
  Compute the full set of decorations for matches in the given
@@ -6960,7 +7034,7 @@ class MatchDecorator {
6960
7034
  */
6961
7035
  createDeco(view) {
6962
7036
  let build = new RangeSetBuilder();
6963
- for (let { from, to } of view.visibleRanges)
7037
+ for (let { from, to } of matchRanges(view, this.maxLength))
6964
7038
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
6965
7039
  return build.finish();
6966
7040
  }