@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.cjs CHANGED
@@ -658,6 +658,7 @@ class TextView extends ContentView {
658
658
  split(from) {
659
659
  let result = new TextView(this.text.slice(from));
660
660
  this.text = this.text.slice(0, from);
661
+ this.markDirty();
661
662
  return result;
662
663
  }
663
664
  localPosFromDOM(node, offset) {
@@ -845,6 +846,9 @@ class CompositionView extends WidgetView {
845
846
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
846
847
  get isEditable() { return true; }
847
848
  }
849
+ // Use two characters on Android, to prevent Chrome from closing the
850
+ // virtual keyboard when backspacing after a widget (#602).
851
+ const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
848
852
  // These are drawn around uneditable widgets to avoid a number of
849
853
  // browser bugs that show up when the cursor is directly next to
850
854
  // uneditable inline content.
@@ -861,12 +865,13 @@ class WidgetBufferView extends ContentView {
861
865
  split() { return new WidgetBufferView(this.side); }
862
866
  sync() {
863
867
  if (!this.dom)
864
- this.setDOM(document.createTextNode("\u200b"));
865
- else if (this.dirty && this.dom.nodeValue != "\u200b")
866
- this.dom.nodeValue = "\u200b";
868
+ this.setDOM(document.createTextNode(ZeroWidthSpace));
869
+ else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
870
+ this.dom.nodeValue = ZeroWidthSpace;
867
871
  }
868
872
  getSide() { return this.side; }
869
873
  domAtPos(pos) { return DOMPos.before(this.dom); }
874
+ localPosFromDOM() { return 0; }
870
875
  domBoundsAround() { return null; }
871
876
  coordsAt(pos) {
872
877
  let rects = clientRectsFor(this.dom);
@@ -1938,798 +1943,874 @@ class ViewUpdate {
1938
1943
  get empty() { return this.flags == 0 && this.transactions.length == 0; }
1939
1944
  }
1940
1945
 
1941
- class DocView extends ContentView {
1942
- constructor(view) {
1943
- super();
1944
- this.view = view;
1945
- this.compositionDeco = Decoration.none;
1946
- this.decorations = [];
1947
- // Track a minimum width for the editor. When measuring sizes in
1948
- // measureVisibleLineHeights, this is updated to point at the width
1949
- // of a given element and its extent in the document. When a change
1950
- // happens in that range, these are reset. That way, once we've seen
1951
- // a line/element of a given length, we keep the editor wide enough
1952
- // to fit at least that element, until it is changed, at which point
1953
- // we forget it again.
1954
- this.minWidth = 0;
1955
- this.minWidthFrom = 0;
1956
- this.minWidthTo = 0;
1957
- // Track whether the DOM selection was set in a lossy way, so that
1958
- // we don't mess it up when reading it back it
1959
- this.impreciseAnchor = null;
1960
- this.impreciseHead = null;
1961
- this.forceSelection = false;
1962
- // Used by the resize observer to ignore resizes that we caused
1963
- // ourselves
1964
- this.lastUpdate = Date.now();
1965
- this.setDOM(view.contentDOM);
1966
- this.children = [new LineView];
1967
- this.children[0].setParent(this);
1968
- this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
1946
+ /**
1947
+ Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
1948
+ */
1949
+ exports.Direction = void 0;
1950
+ (function (Direction) {
1951
+ // (These are chosen to match the base levels, in bidi algorithm
1952
+ // terms, of spans in that direction.)
1953
+ /**
1954
+ Left-to-right.
1955
+ */
1956
+ Direction[Direction["LTR"] = 0] = "LTR";
1957
+ /**
1958
+ Right-to-left.
1959
+ */
1960
+ Direction[Direction["RTL"] = 1] = "RTL";
1961
+ })(exports.Direction || (exports.Direction = {}));
1962
+ const LTR = exports.Direction.LTR, RTL = exports.Direction.RTL;
1963
+ // Decode a string with each type encoded as log2(type)
1964
+ function dec(str) {
1965
+ let result = [];
1966
+ for (let i = 0; i < str.length; i++)
1967
+ result.push(1 << +str[i]);
1968
+ return result;
1969
+ }
1970
+ // Character types for codepoints 0 to 0xf8
1971
+ const LowTypes = dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
1972
+ // Character types for codepoints 0x600 to 0x6f9
1973
+ const ArabicTypes = dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
1974
+ const Brackets = Object.create(null), BracketStack = [];
1975
+ // There's a lot more in
1976
+ // https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
1977
+ // which are left out to keep code size down.
1978
+ for (let p of ["()", "[]", "{}"]) {
1979
+ let l = p.charCodeAt(0), r = p.charCodeAt(1);
1980
+ Brackets[l] = r;
1981
+ Brackets[r] = -l;
1982
+ }
1983
+ function charType(ch) {
1984
+ return ch <= 0xf7 ? LowTypes[ch] :
1985
+ 0x590 <= ch && ch <= 0x5f4 ? 2 /* R */ :
1986
+ 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
1987
+ 0x6ee <= ch && ch <= 0x8ac ? 4 /* AL */ :
1988
+ 0x2000 <= ch && ch <= 0x200b ? 256 /* NI */ :
1989
+ ch == 0x200c ? 256 /* NI */ : 1 /* L */;
1990
+ }
1991
+ const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
1992
+ /**
1993
+ Represents a contiguous range of text that has a single direction
1994
+ (as in left-to-right or right-to-left).
1995
+ */
1996
+ class BidiSpan {
1997
+ /**
1998
+ @internal
1999
+ */
2000
+ constructor(
2001
+ /**
2002
+ The start of the span (relative to the start of the line).
2003
+ */
2004
+ from,
2005
+ /**
2006
+ The end of the span.
2007
+ */
2008
+ to,
2009
+ /**
2010
+ The ["bidi
2011
+ level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
2012
+ of the span (in this context, 0 means
2013
+ left-to-right, 1 means right-to-left, 2 means left-to-right
2014
+ number inside right-to-left text).
2015
+ */
2016
+ level) {
2017
+ this.from = from;
2018
+ this.to = to;
2019
+ this.level = level;
1969
2020
  }
1970
- get root() { return this.view.root; }
1971
- get editorView() { return this.view; }
1972
- get length() { return this.view.state.doc.length; }
1973
- // Update the document view to a given state. scrollIntoView can be
1974
- // used as a hint to compute a new viewport that includes that
1975
- // position, if we know the editor is going to scroll that position
1976
- // into view.
1977
- update(update) {
1978
- let changedRanges = update.changedRanges;
1979
- if (this.minWidth > 0 && changedRanges.length) {
1980
- if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
1981
- this.minWidth = 0;
1982
- }
1983
- else {
1984
- this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
1985
- this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2021
+ /**
2022
+ The direction of this span.
2023
+ */
2024
+ get dir() { return this.level % 2 ? RTL : LTR; }
2025
+ /**
2026
+ @internal
2027
+ */
2028
+ side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
2029
+ /**
2030
+ @internal
2031
+ */
2032
+ static find(order, index, level, assoc) {
2033
+ let maybe = -1;
2034
+ for (let i = 0; i < order.length; i++) {
2035
+ let span = order[i];
2036
+ if (span.from <= index && span.to >= index) {
2037
+ if (span.level == level)
2038
+ return i;
2039
+ // When multiple spans match, if assoc != 0, take the one that
2040
+ // covers that side, otherwise take the one with the minimum
2041
+ // level.
2042
+ if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
2043
+ maybe = i;
1986
2044
  }
1987
2045
  }
1988
- if (this.view.inputState.composing < 0)
1989
- this.compositionDeco = Decoration.none;
1990
- else if (update.transactions.length)
1991
- this.compositionDeco = computeCompositionDeco(this.view, update.changes);
1992
- // When the DOM nodes around the selection are moved to another
1993
- // parent, Chrome sometimes reports a different selection through
1994
- // getSelection than the one that it actually shows to the user.
1995
- // This forces a selection update when lines are joined to work
1996
- // around that. Issue #54
1997
- if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1998
- update.state.doc.lines != update.startState.doc.lines)
1999
- this.forceSelection = true;
2000
- let prevDeco = this.decorations, deco = this.updateDeco();
2001
- let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
2002
- changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
2003
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
2004
- return false;
2005
- }
2006
- else {
2007
- this.updateInner(changedRanges, deco, update.startState.doc.length);
2008
- if (update.transactions.length)
2009
- this.lastUpdate = Date.now();
2010
- return true;
2011
- }
2046
+ if (maybe < 0)
2047
+ throw new RangeError("Index out of range");
2048
+ return maybe;
2012
2049
  }
2013
- reset(sel) {
2014
- if (this.dirty) {
2015
- this.view.observer.ignore(() => this.view.docView.sync());
2016
- this.dirty = 0 /* Not */;
2017
- this.updateSelection(true);
2050
+ }
2051
+ // Reused array of character types
2052
+ const types = [];
2053
+ function computeOrder(line, direction) {
2054
+ let len = line.length, outerType = direction == LTR ? 1 /* L */ : 2 /* R */, oppositeType = direction == LTR ? 2 /* R */ : 1 /* L */;
2055
+ if (!line || outerType == 1 /* L */ && !BidiRE.test(line))
2056
+ return trivialOrder(len);
2057
+ // W1. Examine each non-spacing mark (NSM) in the level run, and
2058
+ // change the type of the NSM to the type of the previous
2059
+ // character. If the NSM is at the start of the level run, it will
2060
+ // get the type of sor.
2061
+ // W2. Search backwards from each instance of a European number
2062
+ // until the first strong type (R, L, AL, or sor) is found. If an
2063
+ // AL is found, change the type of the European number to Arabic
2064
+ // number.
2065
+ // W3. Change all ALs to R.
2066
+ // (Left after this: L, R, EN, AN, ET, CS, NI)
2067
+ for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2068
+ let type = charType(line.charCodeAt(i));
2069
+ if (type == 512 /* NSM */)
2070
+ type = prev;
2071
+ else if (type == 8 /* EN */ && prevStrong == 4 /* AL */)
2072
+ type = 16 /* AN */;
2073
+ types[i] = type == 4 /* AL */ ? 2 /* R */ : type;
2074
+ if (type & 7 /* Strong */)
2075
+ prevStrong = type;
2076
+ prev = type;
2077
+ }
2078
+ // W5. A sequence of European terminators adjacent to European
2079
+ // numbers changes to all European numbers.
2080
+ // W6. Otherwise, separators and terminators change to Other
2081
+ // Neutral.
2082
+ // W7. Search backwards from each instance of a European number
2083
+ // until the first strong type (R, L, or sor) is found. If an L is
2084
+ // found, then change the type of the European number to L.
2085
+ // (Left after this: L, R, EN+AN, NI)
2086
+ for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2087
+ let type = types[i];
2088
+ if (type == 128 /* CS */) {
2089
+ if (i < len - 1 && prev == types[i + 1] && (prev & 24 /* Num */))
2090
+ type = types[i] = prev;
2091
+ else
2092
+ types[i] = 256 /* NI */;
2018
2093
  }
2019
- else {
2020
- this.updateSelection();
2094
+ else if (type == 64 /* ET */) {
2095
+ let end = i + 1;
2096
+ while (end < len && types[end] == 64 /* ET */)
2097
+ end++;
2098
+ let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2099
+ for (let j = i; j < end; j++)
2100
+ types[j] = replace;
2101
+ i = end - 1;
2021
2102
  }
2103
+ else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2104
+ types[i] = 1 /* L */;
2105
+ }
2106
+ prev = type;
2107
+ if (type & 7 /* Strong */)
2108
+ prevStrong = type;
2022
2109
  }
2023
- // Used by update and the constructor do perform the actual DOM
2024
- // update
2025
- updateInner(changes, deco, oldLength) {
2026
- this.view.viewState.mustMeasureContent = true;
2027
- this.updateChildren(changes, deco, oldLength);
2028
- let { observer } = this.view;
2029
- observer.ignore(() => {
2030
- // Lock the height during redrawing, since Chrome sometimes
2031
- // messes with the scroll position during DOM mutation (though
2032
- // no relayout is triggered and I cannot imagine how it can
2033
- // recompute the scroll position without a layout)
2034
- this.dom.style.height = this.view.viewState.contentHeight + "px";
2035
- this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2036
- // Chrome will sometimes, when DOM mutations occur directly
2037
- // around the selection, get confused and report a different
2038
- // selection from the one it displays (issue #218). This tries
2039
- // to detect that situation.
2040
- let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2041
- this.sync(track);
2042
- this.dirty = 0 /* Not */;
2043
- if (track && (track.written || observer.selectionRange.focusNode != track.node))
2044
- this.forceSelection = true;
2045
- this.dom.style.height = "";
2046
- });
2047
- let gaps = [];
2048
- if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2049
- for (let child of this.children)
2050
- if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2051
- gaps.push(child.dom);
2052
- observer.updateGaps(gaps);
2053
- }
2054
- updateChildren(changes, deco, oldLength) {
2055
- let cursor = this.childCursor(oldLength);
2056
- for (let i = changes.length - 1;; i--) {
2057
- let next = i >= 0 ? changes[i] : null;
2058
- if (!next)
2059
- break;
2060
- let { fromA, toA, fromB, toB } = next;
2061
- let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2062
- let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2063
- let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2064
- replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2065
- }
2066
- }
2067
- // Sync the DOM selection to this.state.selection
2068
- updateSelection(mustRead = false, fromPointer = false) {
2069
- if (mustRead)
2070
- this.view.observer.readSelectionRange();
2071
- if (!(fromPointer || this.mayControlSelection()) ||
2072
- browser.ios && this.view.inputState.rapidCompositionStart)
2073
- return;
2074
- let force = this.forceSelection;
2075
- this.forceSelection = false;
2076
- let main = this.view.state.selection.main;
2077
- // FIXME need to handle the case where the selection falls inside a block range
2078
- let anchor = this.domAtPos(main.anchor);
2079
- let head = main.empty ? anchor : this.domAtPos(main.head);
2080
- // Always reset on Firefox when next to an uneditable node to
2081
- // avoid invisible cursor bugs (#111)
2082
- if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2083
- let dummy = document.createTextNode("");
2084
- this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2085
- anchor = head = new DOMPos(dummy, 0);
2086
- force = true;
2087
- }
2088
- let domSel = this.view.observer.selectionRange;
2089
- // If the selection is already here, or in an equivalent position, don't touch it
2090
- if (force || !domSel.focusNode ||
2091
- !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2092
- !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2093
- this.view.observer.ignore(() => {
2094
- // Chrome Android will hide the virtual keyboard when tapping
2095
- // inside an uneditable node, and not bring it back when we
2096
- // move the cursor to its proper position. This tries to
2097
- // restore the keyboard by cycling focus.
2098
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2099
- this.dom.blur();
2100
- this.dom.focus({ preventScroll: true });
2101
- }
2102
- let rawSel = getSelection(this.root);
2103
- if (main.empty) {
2104
- // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2105
- if (browser.gecko) {
2106
- let nextTo = nextToUneditable(anchor.node, anchor.offset);
2107
- if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2108
- let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2109
- if (text)
2110
- anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2111
- }
2110
+ // N0. Process bracket pairs in an isolating run sequence
2111
+ // sequentially in the logical order of the text positions of the
2112
+ // opening paired brackets using the logic given below. Within this
2113
+ // scope, bidirectional types EN and AN are treated as R.
2114
+ for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2115
+ // Keeps [startIndex, type, strongSeen] triples for each open
2116
+ // bracket on BracketStack.
2117
+ if (br = Brackets[ch = line.charCodeAt(i)]) {
2118
+ if (br < 0) { // Closing bracket
2119
+ for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2120
+ if (BracketStack[sJ + 1] == -br) {
2121
+ let flags = BracketStack[sJ + 2];
2122
+ let type = (flags & 2 /* EmbedInside */) ? outerType :
2123
+ !(flags & 4 /* OppositeInside */) ? 0 :
2124
+ (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2125
+ if (type)
2126
+ types[i] = types[BracketStack[sJ]] = type;
2127
+ sI = sJ;
2128
+ break;
2112
2129
  }
2113
- rawSel.collapse(anchor.node, anchor.offset);
2114
- if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2115
- domSel.cursorBidiLevel = main.bidiLevel;
2116
2130
  }
2117
- else if (rawSel.extend) {
2118
- // Selection.extend can be used to create an 'inverted' selection
2119
- // (one where the focus is before the anchor), but not all
2120
- // browsers support it yet.
2121
- rawSel.collapse(anchor.node, anchor.offset);
2122
- rawSel.extend(head.node, head.offset);
2131
+ }
2132
+ else if (BracketStack.length == 189 /* MaxDepth */) {
2133
+ break;
2134
+ }
2135
+ else {
2136
+ BracketStack[sI++] = i;
2137
+ BracketStack[sI++] = ch;
2138
+ BracketStack[sI++] = context;
2139
+ }
2140
+ }
2141
+ else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2142
+ let embed = type == outerType;
2143
+ context = embed ? 0 : 1 /* OppositeBefore */;
2144
+ for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2145
+ let cur = BracketStack[sJ + 2];
2146
+ if (cur & 2 /* EmbedInside */)
2147
+ break;
2148
+ if (embed) {
2149
+ BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2123
2150
  }
2124
2151
  else {
2125
- // Primitive (IE) way
2126
- let range = document.createRange();
2127
- if (main.anchor > main.head)
2128
- [anchor, head] = [head, anchor];
2129
- range.setEnd(head.node, head.offset);
2130
- range.setStart(anchor.node, anchor.offset);
2131
- rawSel.removeAllRanges();
2132
- rawSel.addRange(range);
2152
+ if (cur & 4 /* OppositeInside */)
2153
+ break;
2154
+ BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2133
2155
  }
2134
- });
2135
- this.view.observer.setSelectionRange(anchor, head);
2156
+ }
2136
2157
  }
2137
- this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2138
- this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2139
- }
2140
- enforceCursorAssoc() {
2141
- if (this.view.composing)
2142
- return;
2143
- let cursor = this.view.state.selection.main;
2144
- let sel = getSelection(this.root);
2145
- if (!cursor.empty || !cursor.assoc || !sel.modify)
2146
- return;
2147
- let line = LineView.find(this, cursor.head);
2148
- if (!line)
2149
- return;
2150
- let lineStart = line.posAtStart;
2151
- if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2152
- return;
2153
- let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2154
- if (!before || !after || before.bottom > after.top)
2155
- return;
2156
- let dom = this.domAtPos(cursor.head + cursor.assoc);
2157
- sel.collapse(dom.node, dom.offset);
2158
- sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2159
- }
2160
- mayControlSelection() {
2161
- return this.view.state.facet(editable) ? this.root.activeElement == this.dom
2162
- : hasSelection(this.dom, this.view.observer.selectionRange);
2163
2158
  }
2164
- nearest(dom) {
2165
- for (let cur = dom; cur;) {
2166
- let domView = ContentView.get(cur);
2167
- if (domView && domView.rootView == this)
2168
- return domView;
2169
- cur = cur.parentNode;
2159
+ // N1. A sequence of neutrals takes the direction of the
2160
+ // surrounding strong text if the text on both sides has the same
2161
+ // direction. European and Arabic numbers act as if they were R in
2162
+ // terms of their influence on neutrals. Start-of-level-run (sor)
2163
+ // and end-of-level-run (eor) are used at level run boundaries.
2164
+ // N2. Any remaining neutrals take the embedding direction.
2165
+ // (Left after this: L, R, EN+AN)
2166
+ for (let i = 0; i < len; i++) {
2167
+ if (types[i] == 256 /* NI */) {
2168
+ let end = i + 1;
2169
+ while (end < len && types[end] == 256 /* NI */)
2170
+ end++;
2171
+ let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2172
+ let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2173
+ let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2174
+ for (let j = i; j < end; j++)
2175
+ types[j] = replace;
2176
+ i = end - 1;
2170
2177
  }
2171
- return null;
2172
2178
  }
2173
- posFromDOM(node, offset) {
2174
- let view = this.nearest(node);
2175
- if (!view)
2176
- throw new RangeError("Trying to find position for a DOM position outside of the document");
2177
- return view.localPosFromDOM(node, offset) + view.posAtStart;
2179
+ // Here we depart from the documented algorithm, in order to avoid
2180
+ // building up an actual levels array. Since there are only three
2181
+ // levels (0, 1, 2) in an implementation that doesn't take
2182
+ // explicit embedding into account, we can build up the order on
2183
+ // the fly, without following the level-based algorithm.
2184
+ let order = [];
2185
+ if (outerType == 1 /* L */) {
2186
+ for (let i = 0; i < len;) {
2187
+ let start = i, rtl = types[i++] != 1 /* L */;
2188
+ while (i < len && rtl == (types[i] != 1 /* L */))
2189
+ i++;
2190
+ if (rtl) {
2191
+ for (let j = i; j > start;) {
2192
+ let end = j, l = types[--j] != 2 /* R */;
2193
+ while (j > start && l == (types[j - 1] != 2 /* R */))
2194
+ j--;
2195
+ order.push(new BidiSpan(j, end, l ? 2 : 1));
2196
+ }
2197
+ }
2198
+ else {
2199
+ order.push(new BidiSpan(start, i, 0));
2200
+ }
2201
+ }
2178
2202
  }
2179
- domAtPos(pos) {
2180
- let { i, off } = this.childCursor().findPos(pos, -1);
2181
- for (; i < this.children.length - 1;) {
2182
- let child = this.children[i];
2183
- if (off < child.length || child instanceof LineView)
2184
- break;
2185
- i++;
2186
- off = 0;
2203
+ else {
2204
+ for (let i = 0; i < len;) {
2205
+ let start = i, rtl = types[i++] == 2 /* R */;
2206
+ while (i < len && rtl == (types[i] == 2 /* R */))
2207
+ i++;
2208
+ order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2187
2209
  }
2188
- return this.children[i].domAtPos(off);
2189
2210
  }
2190
- coordsAt(pos, side) {
2191
- for (let off = this.length, i = this.children.length - 1;; i--) {
2192
- let child = this.children[i], start = off - child.breakAfter - child.length;
2193
- if (pos > start ||
2194
- (pos == start && child.type != exports.BlockType.WidgetBefore && child.type != exports.BlockType.WidgetAfter &&
2195
- (!i || side == 2 || this.children[i - 1].breakAfter ||
2196
- (this.children[i - 1].type == exports.BlockType.WidgetBefore && side > -2))))
2197
- return child.coordsAt(pos - start, side);
2198
- off = start;
2211
+ return order;
2212
+ }
2213
+ function trivialOrder(length) {
2214
+ return [new BidiSpan(0, length, 0)];
2215
+ }
2216
+ let movedOver = "";
2217
+ function moveVisually(line, order, dir, start, forward) {
2218
+ var _a;
2219
+ let startIndex = start.head - line.from, spanI = -1;
2220
+ if (startIndex == 0) {
2221
+ if (!forward || !line.length)
2222
+ return null;
2223
+ if (order[0].level != dir) {
2224
+ startIndex = order[0].side(false, dir);
2225
+ spanI = 0;
2199
2226
  }
2200
2227
  }
2201
- measureVisibleLineHeights() {
2202
- let result = [], { from, to } = this.view.viewState.viewport;
2203
- let minWidth = Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2204
- for (let pos = 0, i = 0; i < this.children.length; i++) {
2205
- let child = this.children[i], end = pos + child.length;
2206
- if (end > to)
2207
- break;
2208
- if (pos >= from) {
2209
- result.push(child.dom.getBoundingClientRect().height);
2210
- let width = child.dom.scrollWidth;
2211
- if (width > minWidth) {
2212
- this.minWidth = minWidth = width;
2213
- this.minWidthFrom = pos;
2214
- this.minWidthTo = end;
2215
- }
2216
- }
2217
- pos = end + child.breakAfter;
2228
+ else if (startIndex == line.length) {
2229
+ if (forward)
2230
+ return null;
2231
+ let last = order[order.length - 1];
2232
+ if (last.level != dir) {
2233
+ startIndex = last.side(true, dir);
2234
+ spanI = order.length - 1;
2218
2235
  }
2219
- return result;
2220
2236
  }
2221
- measureTextSize() {
2222
- for (let child of this.children) {
2223
- if (child instanceof LineView) {
2224
- let measure = child.measureTextSize();
2225
- if (measure)
2226
- return measure;
2227
- }
2228
- }
2229
- // If no workable line exists, force a layout of a measurable element
2230
- let dummy = document.createElement("div"), lineHeight, charWidth;
2231
- dummy.className = "cm-line";
2232
- dummy.textContent = "abc def ghi jkl mno pqr stu";
2233
- this.view.observer.ignore(() => {
2234
- this.dom.appendChild(dummy);
2235
- let rect = clientRectsFor(dummy.firstChild)[0];
2236
- lineHeight = dummy.getBoundingClientRect().height;
2237
- charWidth = rect ? rect.width / 27 : 7;
2238
- dummy.remove();
2239
- });
2240
- return { lineHeight, charWidth };
2237
+ if (spanI < 0)
2238
+ spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2239
+ let span = order[spanI];
2240
+ // End of span. (But not end of line--that was checked for above.)
2241
+ if (startIndex == span.side(forward, dir)) {
2242
+ span = order[spanI += forward ? 1 : -1];
2243
+ startIndex = span.side(!forward, dir);
2241
2244
  }
2242
- childCursor(pos = this.length) {
2243
- // Move back to start of last element when possible, so that
2244
- // `ChildCursor.findPos` doesn't have to deal with the edge case
2245
- // of being after the last element.
2246
- let i = this.children.length;
2247
- if (i)
2248
- pos -= this.children[--i].length;
2249
- return new ChildCursor(this.children, pos, i);
2245
+ let indexForward = forward == (span.dir == dir);
2246
+ let nextIndex = text.findClusterBreak(line.text, startIndex, indexForward);
2247
+ movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2248
+ if (nextIndex != span.side(forward, dir))
2249
+ return state.EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2250
+ let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2251
+ if (!nextSpan && span.level != dir)
2252
+ return state.EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2253
+ if (nextSpan && nextSpan.level < span.level)
2254
+ return state.EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2255
+ return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2256
+ }
2257
+
2258
+ class DOMReader {
2259
+ constructor(points, view) {
2260
+ this.points = points;
2261
+ this.view = view;
2262
+ this.text = "";
2263
+ this.lineBreak = view.state.lineBreak;
2250
2264
  }
2251
- computeBlockGapDeco() {
2252
- let deco = [], vs = this.view.viewState;
2253
- for (let pos = 0, i = 0;; i++) {
2254
- let next = i == vs.viewports.length ? null : vs.viewports[i];
2255
- let end = next ? next.from - 1 : this.length;
2256
- if (end > pos) {
2257
- let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2258
- deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2259
- }
2260
- if (!next)
2265
+ readRange(start, end) {
2266
+ if (!start)
2267
+ return this;
2268
+ let parent = start.parentNode;
2269
+ for (let cur = start;;) {
2270
+ this.findPointBefore(parent, cur);
2271
+ this.readNode(cur);
2272
+ let next = cur.nextSibling;
2273
+ if (next == end)
2261
2274
  break;
2262
- pos = next.to + 1;
2275
+ let view = ContentView.get(cur), nextView = ContentView.get(next);
2276
+ if (view && nextView ? view.breakAfter :
2277
+ (view ? view.breakAfter : isBlockElement(cur)) ||
2278
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2279
+ this.text += this.lineBreak;
2280
+ cur = next;
2263
2281
  }
2264
- return Decoration.set(deco);
2265
- }
2266
- updateDeco() {
2267
- return this.decorations = [
2268
- ...this.view.pluginField(PluginField.decorations),
2269
- ...this.view.state.facet(decorations),
2270
- this.compositionDeco,
2271
- this.computeBlockGapDeco(),
2272
- this.view.viewState.lineGapDeco
2273
- ];
2282
+ this.findPointBefore(parent, end);
2283
+ return this;
2274
2284
  }
2275
- scrollIntoView({ range, center }) {
2276
- let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2277
- if (!rect)
2285
+ readNode(node) {
2286
+ if (node.cmIgnore)
2278
2287
  return;
2279
- if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2280
- rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2281
- right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2282
- let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2283
- for (let margins of this.view.pluginField(PluginField.scrollMargins))
2284
- if (margins) {
2285
- let { left, right, top, bottom } = margins;
2286
- if (left != null)
2287
- mLeft = Math.max(mLeft, left);
2288
- if (right != null)
2289
- mRight = Math.max(mRight, right);
2290
- if (top != null)
2291
- mTop = Math.max(mTop, top);
2292
- if (bottom != null)
2293
- mBottom = Math.max(mBottom, bottom);
2294
- }
2295
- scrollRectIntoView(this.view.scrollDOM, {
2296
- left: rect.left - mLeft, top: rect.top - mTop,
2297
- right: rect.right + mRight, bottom: rect.bottom + mBottom
2298
- }, range.head < range.anchor ? -1 : 1, center);
2299
- }
2300
- }
2301
- function betweenUneditable(pos) {
2302
- return pos.node.nodeType == 1 && pos.node.firstChild &&
2303
- (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2304
- (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2305
- }
2306
- class BlockGapWidget extends WidgetType {
2307
- constructor(height) {
2308
- super();
2309
- this.height = height;
2310
- }
2311
- toDOM() {
2312
- let elt = document.createElement("div");
2313
- this.updateDOM(elt);
2314
- return elt;
2315
- }
2316
- eq(other) { return other.height == this.height; }
2317
- updateDOM(elt) {
2318
- elt.style.height = this.height + "px";
2319
- return true;
2320
- }
2321
- get estimatedHeight() { return this.height; }
2322
- }
2323
- function computeCompositionDeco(view, changes) {
2324
- let sel = view.observer.selectionRange;
2325
- let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2326
- if (!textNode)
2327
- return Decoration.none;
2328
- let cView = view.docView.nearest(textNode);
2329
- if (!cView)
2330
- return Decoration.none;
2331
- let from, to, topNode = textNode;
2332
- if (cView instanceof LineView) {
2333
- while (topNode.parentNode != cView.dom)
2334
- topNode = topNode.parentNode;
2335
- let prev = topNode.previousSibling;
2336
- while (prev && !ContentView.get(prev))
2337
- prev = prev.previousSibling;
2338
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2339
- }
2340
- else {
2341
- for (;;) {
2342
- let { parent } = cView;
2343
- if (!parent)
2344
- return Decoration.none;
2345
- if (parent instanceof LineView)
2346
- break;
2347
- cView = parent;
2288
+ let view = ContentView.get(node);
2289
+ let fromView = view && view.overrideDOMText;
2290
+ let text;
2291
+ if (fromView != null)
2292
+ text = fromView.sliceString(0, undefined, this.lineBreak);
2293
+ else if (node.nodeType == 3)
2294
+ text = node.nodeValue;
2295
+ else if (node.nodeName == "BR")
2296
+ text = node.nextSibling ? this.lineBreak : "";
2297
+ else if (node.nodeType == 1)
2298
+ this.readRange(node.firstChild, null);
2299
+ if (text != null) {
2300
+ this.findPointIn(node, text.length);
2301
+ this.text += text;
2302
+ // Chrome inserts two newlines when pressing shift-enter at the
2303
+ // end of a line. This drops one of those.
2304
+ if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2305
+ this.text = this.text.slice(0, -1);
2348
2306
  }
2349
- from = cView.posAtStart;
2350
- to = from + cView.length;
2351
- topNode = cView.dom;
2352
- }
2353
- let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2354
- let text = textNode.nodeValue, { state } = view;
2355
- if (newTo - newFrom < text.length) {
2356
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2357
- newTo = newFrom + text.length;
2358
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2359
- newFrom = newTo - text.length;
2360
- else
2361
- return Decoration.none;
2362
- }
2363
- else if (state.sliceDoc(newFrom, newTo) != text) {
2364
- return Decoration.none;
2365
2307
  }
2366
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2367
- }
2368
- class CompositionWidget extends WidgetType {
2369
- constructor(top, text) {
2370
- super();
2371
- this.top = top;
2372
- this.text = text;
2308
+ findPointBefore(node, next) {
2309
+ for (let point of this.points)
2310
+ if (point.node == node && node.childNodes[point.offset] == next)
2311
+ point.pos = this.text.length;
2373
2312
  }
2374
- eq(other) { return this.top == other.top && this.text == other.text; }
2375
- toDOM() { return this.top; }
2376
- ignoreEvent() { return false; }
2377
- get customView() { return CompositionView; }
2378
- }
2379
- function nearbyTextNode(node, offset, side) {
2380
- for (;;) {
2381
- if (node.nodeType == 3)
2382
- return node;
2383
- if (node.nodeType == 1 && offset > 0 && side <= 0) {
2384
- node = node.childNodes[offset - 1];
2385
- offset = maxOffset(node);
2386
- }
2387
- else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2388
- node = node.childNodes[offset];
2389
- offset = 0;
2390
- }
2391
- else {
2392
- return null;
2393
- }
2313
+ findPointIn(node, maxLen) {
2314
+ for (let point of this.points)
2315
+ if (point.node == node)
2316
+ point.pos = this.text.length + Math.min(point.offset, maxLen);
2394
2317
  }
2395
2318
  }
2396
- function nextToUneditable(node, offset) {
2397
- if (node.nodeType != 1)
2398
- return 0;
2399
- return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2400
- (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2319
+ function isBlockElement(node) {
2320
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
2401
2321
  }
2402
- class DecorationComparator$1 {
2403
- constructor() {
2404
- this.changes = [];
2322
+ class DOMPoint {
2323
+ constructor(node, offset) {
2324
+ this.node = node;
2325
+ this.offset = offset;
2326
+ this.pos = -1;
2405
2327
  }
2406
- compareRange(from, to) { addRange(from, to, this.changes); }
2407
- comparePoint(from, to) { addRange(from, to, this.changes); }
2408
2328
  }
2409
- function findChangedDeco(a, b, diff) {
2410
- let comp = new DecorationComparator$1;
2411
- rangeset.RangeSet.compare(a, b, diff, comp);
2412
- return comp.changes;
2413
- }
2414
- function inUneditable(node, inside) {
2415
- for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2416
- if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2329
+
2330
+ class DocView extends ContentView {
2331
+ constructor(view) {
2332
+ super();
2333
+ this.view = view;
2334
+ this.compositionDeco = Decoration.none;
2335
+ this.decorations = [];
2336
+ // Track a minimum width for the editor. When measuring sizes in
2337
+ // measureVisibleLineHeights, this is updated to point at the width
2338
+ // of a given element and its extent in the document. When a change
2339
+ // happens in that range, these are reset. That way, once we've seen
2340
+ // a line/element of a given length, we keep the editor wide enough
2341
+ // to fit at least that element, until it is changed, at which point
2342
+ // we forget it again.
2343
+ this.minWidth = 0;
2344
+ this.minWidthFrom = 0;
2345
+ this.minWidthTo = 0;
2346
+ // Track whether the DOM selection was set in a lossy way, so that
2347
+ // we don't mess it up when reading it back it
2348
+ this.impreciseAnchor = null;
2349
+ this.impreciseHead = null;
2350
+ this.forceSelection = false;
2351
+ // Used by the resize observer to ignore resizes that we caused
2352
+ // ourselves
2353
+ this.lastUpdate = Date.now();
2354
+ this.setDOM(view.contentDOM);
2355
+ this.children = [new LineView];
2356
+ this.children[0].setParent(this);
2357
+ this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
2358
+ }
2359
+ get root() { return this.view.root; }
2360
+ get editorView() { return this.view; }
2361
+ get length() { return this.view.state.doc.length; }
2362
+ // Update the document view to a given state. scrollIntoView can be
2363
+ // used as a hint to compute a new viewport that includes that
2364
+ // position, if we know the editor is going to scroll that position
2365
+ // into view.
2366
+ update(update) {
2367
+ let changedRanges = update.changedRanges;
2368
+ if (this.minWidth > 0 && changedRanges.length) {
2369
+ if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
2370
+ this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
2371
+ }
2372
+ else {
2373
+ this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
2374
+ this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2375
+ }
2376
+ }
2377
+ if (this.view.inputState.composing < 0)
2378
+ this.compositionDeco = Decoration.none;
2379
+ else if (update.transactions.length || this.dirty)
2380
+ this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2381
+ // When the DOM nodes around the selection are moved to another
2382
+ // parent, Chrome sometimes reports a different selection through
2383
+ // getSelection than the one that it actually shows to the user.
2384
+ // This forces a selection update when lines are joined to work
2385
+ // around that. Issue #54
2386
+ if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
2387
+ update.state.doc.lines != update.startState.doc.lines)
2388
+ this.forceSelection = true;
2389
+ let prevDeco = this.decorations, deco = this.updateDeco();
2390
+ let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
2391
+ changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
2392
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
2393
+ return false;
2394
+ }
2395
+ else {
2396
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
2397
+ if (update.transactions.length)
2398
+ this.lastUpdate = Date.now();
2417
2399
  return true;
2418
2400
  }
2419
2401
  }
2420
- return false;
2421
- }
2422
-
2423
- /**
2424
- Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
2425
- */
2426
- exports.Direction = void 0;
2427
- (function (Direction) {
2428
- // (These are chosen to match the base levels, in bidi algorithm
2429
- // terms, of spans in that direction.)
2430
- /**
2431
- Left-to-right.
2432
- */
2433
- Direction[Direction["LTR"] = 0] = "LTR";
2434
- /**
2435
- Right-to-left.
2436
- */
2437
- Direction[Direction["RTL"] = 1] = "RTL";
2438
- })(exports.Direction || (exports.Direction = {}));
2439
- const LTR = exports.Direction.LTR, RTL = exports.Direction.RTL;
2440
- // Decode a string with each type encoded as log2(type)
2441
- function dec(str) {
2442
- let result = [];
2443
- for (let i = 0; i < str.length; i++)
2444
- result.push(1 << +str[i]);
2445
- return result;
2446
- }
2447
- // Character types for codepoints 0 to 0xf8
2448
- const LowTypes = dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
2449
- // Character types for codepoints 0x600 to 0x6f9
2450
- const ArabicTypes = dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
2451
- const Brackets = Object.create(null), BracketStack = [];
2452
- // There's a lot more in
2453
- // https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
2454
- // which are left out to keep code size down.
2455
- for (let p of ["()", "[]", "{}"]) {
2456
- let l = p.charCodeAt(0), r = p.charCodeAt(1);
2457
- Brackets[l] = r;
2458
- Brackets[r] = -l;
2459
- }
2460
- function charType(ch) {
2461
- return ch <= 0xf7 ? LowTypes[ch] :
2462
- 0x590 <= ch && ch <= 0x5f4 ? 2 /* R */ :
2463
- 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
2464
- 0x6ee <= ch && ch <= 0x8ac ? 4 /* AL */ :
2465
- 0x2000 <= ch && ch <= 0x200b ? 256 /* NI */ :
2466
- ch == 0x200c ? 256 /* NI */ : 1 /* L */;
2467
- }
2468
- const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
2469
- /**
2470
- Represents a contiguous range of text that has a single direction
2471
- (as in left-to-right or right-to-left).
2472
- */
2473
- class BidiSpan {
2474
- /**
2475
- @internal
2476
- */
2477
- constructor(
2478
- /**
2479
- The start of the span (relative to the start of the line).
2480
- */
2481
- from,
2482
- /**
2483
- The end of the span.
2484
- */
2485
- to,
2486
- /**
2487
- The ["bidi
2488
- level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
2489
- of the span (in this context, 0 means
2490
- left-to-right, 1 means right-to-left, 2 means left-to-right
2491
- number inside right-to-left text).
2492
- */
2493
- level) {
2494
- this.from = from;
2495
- this.to = to;
2496
- this.level = level;
2402
+ // Used by update and the constructor do perform the actual DOM
2403
+ // update
2404
+ updateInner(changes, deco, oldLength) {
2405
+ this.view.viewState.mustMeasureContent = true;
2406
+ this.updateChildren(changes, deco, oldLength);
2407
+ let { observer } = this.view;
2408
+ observer.ignore(() => {
2409
+ // Lock the height during redrawing, since Chrome sometimes
2410
+ // messes with the scroll position during DOM mutation (though
2411
+ // no relayout is triggered and I cannot imagine how it can
2412
+ // recompute the scroll position without a layout)
2413
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2414
+ this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2415
+ // Chrome will sometimes, when DOM mutations occur directly
2416
+ // around the selection, get confused and report a different
2417
+ // selection from the one it displays (issue #218). This tries
2418
+ // to detect that situation.
2419
+ let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2420
+ this.sync(track);
2421
+ this.dirty = 0 /* Not */;
2422
+ if (track && (track.written || observer.selectionRange.focusNode != track.node))
2423
+ this.forceSelection = true;
2424
+ this.dom.style.height = "";
2425
+ });
2426
+ let gaps = [];
2427
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2428
+ for (let child of this.children)
2429
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2430
+ gaps.push(child.dom);
2431
+ observer.updateGaps(gaps);
2497
2432
  }
2498
- /**
2499
- The direction of this span.
2500
- */
2501
- get dir() { return this.level % 2 ? RTL : LTR; }
2502
- /**
2503
- @internal
2504
- */
2505
- side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
2506
- /**
2507
- @internal
2508
- */
2509
- static find(order, index, level, assoc) {
2510
- let maybe = -1;
2511
- for (let i = 0; i < order.length; i++) {
2512
- let span = order[i];
2513
- if (span.from <= index && span.to >= index) {
2514
- if (span.level == level)
2515
- return i;
2516
- // When multiple spans match, if assoc != 0, take the one that
2517
- // covers that side, otherwise take the one with the minimum
2518
- // level.
2519
- if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
2520
- maybe = i;
2521
- }
2433
+ updateChildren(changes, deco, oldLength) {
2434
+ let cursor = this.childCursor(oldLength);
2435
+ for (let i = changes.length - 1;; i--) {
2436
+ let next = i >= 0 ? changes[i] : null;
2437
+ if (!next)
2438
+ break;
2439
+ let { fromA, toA, fromB, toB } = next;
2440
+ let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2441
+ let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2442
+ let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2443
+ replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2522
2444
  }
2523
- if (maybe < 0)
2524
- throw new RangeError("Index out of range");
2525
- return maybe;
2526
2445
  }
2527
- }
2528
- // Reused array of character types
2529
- const types = [];
2530
- function computeOrder(line, direction) {
2531
- let len = line.length, outerType = direction == LTR ? 1 /* L */ : 2 /* R */, oppositeType = direction == LTR ? 2 /* R */ : 1 /* L */;
2532
- if (!line || outerType == 1 /* L */ && !BidiRE.test(line))
2533
- return trivialOrder(len);
2534
- // W1. Examine each non-spacing mark (NSM) in the level run, and
2535
- // change the type of the NSM to the type of the previous
2536
- // character. If the NSM is at the start of the level run, it will
2537
- // get the type of sor.
2538
- // W2. Search backwards from each instance of a European number
2539
- // until the first strong type (R, L, AL, or sor) is found. If an
2540
- // AL is found, change the type of the European number to Arabic
2541
- // number.
2542
- // W3. Change all ALs to R.
2543
- // (Left after this: L, R, EN, AN, ET, CS, NI)
2544
- for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2545
- let type = charType(line.charCodeAt(i));
2546
- if (type == 512 /* NSM */)
2547
- type = prev;
2548
- else if (type == 8 /* EN */ && prevStrong == 4 /* AL */)
2549
- type = 16 /* AN */;
2550
- types[i] = type == 4 /* AL */ ? 2 /* R */ : type;
2551
- if (type & 7 /* Strong */)
2552
- prevStrong = type;
2553
- prev = type;
2446
+ // Sync the DOM selection to this.state.selection
2447
+ updateSelection(mustRead = false, fromPointer = false) {
2448
+ if (mustRead)
2449
+ this.view.observer.readSelectionRange();
2450
+ if (!(fromPointer || this.mayControlSelection()) ||
2451
+ browser.ios && this.view.inputState.rapidCompositionStart)
2452
+ return;
2453
+ let force = this.forceSelection;
2454
+ this.forceSelection = false;
2455
+ let main = this.view.state.selection.main;
2456
+ // FIXME need to handle the case where the selection falls inside a block range
2457
+ let anchor = this.domAtPos(main.anchor);
2458
+ let head = main.empty ? anchor : this.domAtPos(main.head);
2459
+ // Always reset on Firefox when next to an uneditable node to
2460
+ // avoid invisible cursor bugs (#111)
2461
+ if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2462
+ let dummy = document.createTextNode("");
2463
+ this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2464
+ anchor = head = new DOMPos(dummy, 0);
2465
+ force = true;
2466
+ }
2467
+ let domSel = this.view.observer.selectionRange;
2468
+ // If the selection is already here, or in an equivalent position, don't touch it
2469
+ if (force || !domSel.focusNode ||
2470
+ !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2471
+ !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2472
+ this.view.observer.ignore(() => {
2473
+ // Chrome Android will hide the virtual keyboard when tapping
2474
+ // inside an uneditable node, and not bring it back when we
2475
+ // move the cursor to its proper position. This tries to
2476
+ // restore the keyboard by cycling focus.
2477
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
2478
+ inUneditable(domSel.focusNode, this.dom)) {
2479
+ this.dom.blur();
2480
+ this.dom.focus({ preventScroll: true });
2481
+ }
2482
+ let rawSel = getSelection(this.root);
2483
+ if (main.empty) {
2484
+ // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2485
+ if (browser.gecko) {
2486
+ let nextTo = nextToUneditable(anchor.node, anchor.offset);
2487
+ if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2488
+ let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2489
+ if (text)
2490
+ anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2491
+ }
2492
+ }
2493
+ rawSel.collapse(anchor.node, anchor.offset);
2494
+ if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2495
+ domSel.cursorBidiLevel = main.bidiLevel;
2496
+ }
2497
+ else if (rawSel.extend) {
2498
+ // Selection.extend can be used to create an 'inverted' selection
2499
+ // (one where the focus is before the anchor), but not all
2500
+ // browsers support it yet.
2501
+ rawSel.collapse(anchor.node, anchor.offset);
2502
+ rawSel.extend(head.node, head.offset);
2503
+ }
2504
+ else {
2505
+ // Primitive (IE) way
2506
+ let range = document.createRange();
2507
+ if (main.anchor > main.head)
2508
+ [anchor, head] = [head, anchor];
2509
+ range.setEnd(head.node, head.offset);
2510
+ range.setStart(anchor.node, anchor.offset);
2511
+ rawSel.removeAllRanges();
2512
+ rawSel.addRange(range);
2513
+ }
2514
+ });
2515
+ this.view.observer.setSelectionRange(anchor, head);
2516
+ }
2517
+ this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2518
+ this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2519
+ }
2520
+ enforceCursorAssoc() {
2521
+ if (this.compositionDeco.size)
2522
+ return;
2523
+ let cursor = this.view.state.selection.main;
2524
+ let sel = getSelection(this.root);
2525
+ if (!cursor.empty || !cursor.assoc || !sel.modify)
2526
+ return;
2527
+ let line = LineView.find(this, cursor.head);
2528
+ if (!line)
2529
+ return;
2530
+ let lineStart = line.posAtStart;
2531
+ if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2532
+ return;
2533
+ let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2534
+ if (!before || !after || before.bottom > after.top)
2535
+ return;
2536
+ let dom = this.domAtPos(cursor.head + cursor.assoc);
2537
+ sel.collapse(dom.node, dom.offset);
2538
+ sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2554
2539
  }
2555
- // W5. A sequence of European terminators adjacent to European
2556
- // numbers changes to all European numbers.
2557
- // W6. Otherwise, separators and terminators change to Other
2558
- // Neutral.
2559
- // W7. Search backwards from each instance of a European number
2560
- // until the first strong type (R, L, or sor) is found. If an L is
2561
- // found, then change the type of the European number to L.
2562
- // (Left after this: L, R, EN+AN, NI)
2563
- for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2564
- let type = types[i];
2565
- if (type == 128 /* CS */) {
2566
- if (i < len - 1 && prev == types[i + 1] && (prev & 24 /* Num */))
2567
- type = types[i] = prev;
2568
- else
2569
- types[i] = 256 /* NI */;
2540
+ mayControlSelection() {
2541
+ return this.view.state.facet(editable) ? this.root.activeElement == this.dom
2542
+ : hasSelection(this.dom, this.view.observer.selectionRange);
2543
+ }
2544
+ nearest(dom) {
2545
+ for (let cur = dom; cur;) {
2546
+ let domView = ContentView.get(cur);
2547
+ if (domView && domView.rootView == this)
2548
+ return domView;
2549
+ cur = cur.parentNode;
2570
2550
  }
2571
- else if (type == 64 /* ET */) {
2572
- let end = i + 1;
2573
- while (end < len && types[end] == 64 /* ET */)
2574
- end++;
2575
- let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2576
- for (let j = i; j < end; j++)
2577
- types[j] = replace;
2578
- i = end - 1;
2551
+ return null;
2552
+ }
2553
+ posFromDOM(node, offset) {
2554
+ let view = this.nearest(node);
2555
+ if (!view)
2556
+ throw new RangeError("Trying to find position for a DOM position outside of the document");
2557
+ return view.localPosFromDOM(node, offset) + view.posAtStart;
2558
+ }
2559
+ domAtPos(pos) {
2560
+ let { i, off } = this.childCursor().findPos(pos, -1);
2561
+ for (; i < this.children.length - 1;) {
2562
+ let child = this.children[i];
2563
+ if (off < child.length || child instanceof LineView)
2564
+ break;
2565
+ i++;
2566
+ off = 0;
2579
2567
  }
2580
- else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2581
- types[i] = 1 /* L */;
2568
+ return this.children[i].domAtPos(off);
2569
+ }
2570
+ coordsAt(pos, side) {
2571
+ for (let off = this.length, i = this.children.length - 1;; i--) {
2572
+ let child = this.children[i], start = off - child.breakAfter - child.length;
2573
+ if (pos > start ||
2574
+ (pos == start && child.type != exports.BlockType.WidgetBefore && child.type != exports.BlockType.WidgetAfter &&
2575
+ (!i || side == 2 || this.children[i - 1].breakAfter ||
2576
+ (this.children[i - 1].type == exports.BlockType.WidgetBefore && side > -2))))
2577
+ return child.coordsAt(pos - start, side);
2578
+ off = start;
2582
2579
  }
2583
- prev = type;
2584
- if (type & 7 /* Strong */)
2585
- prevStrong = type;
2586
2580
  }
2587
- // N0. Process bracket pairs in an isolating run sequence
2588
- // sequentially in the logical order of the text positions of the
2589
- // opening paired brackets using the logic given below. Within this
2590
- // scope, bidirectional types EN and AN are treated as R.
2591
- for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2592
- // Keeps [startIndex, type, strongSeen] triples for each open
2593
- // bracket on BracketStack.
2594
- if (br = Brackets[ch = line.charCodeAt(i)]) {
2595
- if (br < 0) { // Closing bracket
2596
- for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2597
- if (BracketStack[sJ + 1] == -br) {
2598
- let flags = BracketStack[sJ + 2];
2599
- let type = (flags & 2 /* EmbedInside */) ? outerType :
2600
- !(flags & 4 /* OppositeInside */) ? 0 :
2601
- (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2602
- if (type)
2603
- types[i] = types[BracketStack[sJ]] = type;
2604
- sI = sJ;
2605
- break;
2581
+ measureVisibleLineHeights() {
2582
+ let result = [], { from, to } = this.view.viewState.viewport;
2583
+ let contentWidth = this.view.contentDOM.clientWidth;
2584
+ let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2585
+ let widest = -1;
2586
+ for (let pos = 0, i = 0; i < this.children.length; i++) {
2587
+ let child = this.children[i], end = pos + child.length;
2588
+ if (end > to)
2589
+ break;
2590
+ if (pos >= from) {
2591
+ let childRect = child.dom.getBoundingClientRect();
2592
+ result.push(childRect.height);
2593
+ if (isWider) {
2594
+ let last = child.dom.lastChild;
2595
+ let rects = last ? clientRectsFor(last) : [];
2596
+ if (rects.length) {
2597
+ let rect = rects[rects.length - 1];
2598
+ let width = this.view.textDirection == exports.Direction.LTR ? rect.right - childRect.left
2599
+ : childRect.right - rect.left;
2600
+ if (width > widest) {
2601
+ widest = width;
2602
+ this.minWidth = contentWidth;
2603
+ this.minWidthFrom = pos;
2604
+ this.minWidthTo = end;
2605
+ }
2606
2606
  }
2607
2607
  }
2608
2608
  }
2609
- else if (BracketStack.length == 189 /* MaxDepth */) {
2610
- break;
2611
- }
2612
- else {
2613
- BracketStack[sI++] = i;
2614
- BracketStack[sI++] = ch;
2615
- BracketStack[sI++] = context;
2616
- }
2609
+ pos = end + child.breakAfter;
2617
2610
  }
2618
- else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2619
- let embed = type == outerType;
2620
- context = embed ? 0 : 1 /* OppositeBefore */;
2621
- for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2622
- let cur = BracketStack[sJ + 2];
2623
- if (cur & 2 /* EmbedInside */)
2624
- break;
2625
- if (embed) {
2626
- BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2627
- }
2628
- else {
2629
- if (cur & 4 /* OppositeInside */)
2630
- break;
2631
- BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2632
- }
2611
+ return result;
2612
+ }
2613
+ measureTextSize() {
2614
+ for (let child of this.children) {
2615
+ if (child instanceof LineView) {
2616
+ let measure = child.measureTextSize();
2617
+ if (measure)
2618
+ return measure;
2633
2619
  }
2634
2620
  }
2621
+ // If no workable line exists, force a layout of a measurable element
2622
+ let dummy = document.createElement("div"), lineHeight, charWidth;
2623
+ dummy.className = "cm-line";
2624
+ dummy.textContent = "abc def ghi jkl mno pqr stu";
2625
+ this.view.observer.ignore(() => {
2626
+ this.dom.appendChild(dummy);
2627
+ let rect = clientRectsFor(dummy.firstChild)[0];
2628
+ lineHeight = dummy.getBoundingClientRect().height;
2629
+ charWidth = rect ? rect.width / 27 : 7;
2630
+ dummy.remove();
2631
+ });
2632
+ return { lineHeight, charWidth };
2635
2633
  }
2636
- // N1. A sequence of neutrals takes the direction of the
2637
- // surrounding strong text if the text on both sides has the same
2638
- // direction. European and Arabic numbers act as if they were R in
2639
- // terms of their influence on neutrals. Start-of-level-run (sor)
2640
- // and end-of-level-run (eor) are used at level run boundaries.
2641
- // N2. Any remaining neutrals take the embedding direction.
2642
- // (Left after this: L, R, EN+AN)
2643
- for (let i = 0; i < len; i++) {
2644
- if (types[i] == 256 /* NI */) {
2645
- let end = i + 1;
2646
- while (end < len && types[end] == 256 /* NI */)
2647
- end++;
2648
- let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2649
- let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2650
- let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2651
- for (let j = i; j < end; j++)
2652
- types[j] = replace;
2653
- i = end - 1;
2654
- }
2634
+ childCursor(pos = this.length) {
2635
+ // Move back to start of last element when possible, so that
2636
+ // `ChildCursor.findPos` doesn't have to deal with the edge case
2637
+ // of being after the last element.
2638
+ let i = this.children.length;
2639
+ if (i)
2640
+ pos -= this.children[--i].length;
2641
+ return new ChildCursor(this.children, pos, i);
2655
2642
  }
2656
- // Here we depart from the documented algorithm, in order to avoid
2657
- // building up an actual levels array. Since there are only three
2658
- // levels (0, 1, 2) in an implementation that doesn't take
2659
- // explicit embedding into account, we can build up the order on
2660
- // the fly, without following the level-based algorithm.
2661
- let order = [];
2662
- if (outerType == 1 /* L */) {
2663
- for (let i = 0; i < len;) {
2664
- let start = i, rtl = types[i++] != 1 /* L */;
2665
- while (i < len && rtl == (types[i] != 1 /* L */))
2666
- i++;
2667
- if (rtl) {
2668
- for (let j = i; j > start;) {
2669
- let end = j, l = types[--j] != 2 /* R */;
2670
- while (j > start && l == (types[j - 1] != 2 /* R */))
2671
- j--;
2672
- order.push(new BidiSpan(j, end, l ? 2 : 1));
2673
- }
2674
- }
2675
- else {
2676
- order.push(new BidiSpan(start, i, 0));
2643
+ computeBlockGapDeco() {
2644
+ let deco = [], vs = this.view.viewState;
2645
+ for (let pos = 0, i = 0;; i++) {
2646
+ let next = i == vs.viewports.length ? null : vs.viewports[i];
2647
+ let end = next ? next.from - 1 : this.length;
2648
+ if (end > pos) {
2649
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2650
+ deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2677
2651
  }
2652
+ if (!next)
2653
+ break;
2654
+ pos = next.to + 1;
2678
2655
  }
2656
+ return Decoration.set(deco);
2657
+ }
2658
+ updateDeco() {
2659
+ return this.decorations = [
2660
+ ...this.view.pluginField(PluginField.decorations),
2661
+ ...this.view.state.facet(decorations),
2662
+ this.compositionDeco,
2663
+ this.computeBlockGapDeco(),
2664
+ this.view.viewState.lineGapDeco
2665
+ ];
2666
+ }
2667
+ scrollIntoView({ range, center }) {
2668
+ let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2669
+ if (!rect)
2670
+ return;
2671
+ if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2672
+ rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2673
+ right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2674
+ let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2675
+ for (let margins of this.view.pluginField(PluginField.scrollMargins))
2676
+ if (margins) {
2677
+ let { left, right, top, bottom } = margins;
2678
+ if (left != null)
2679
+ mLeft = Math.max(mLeft, left);
2680
+ if (right != null)
2681
+ mRight = Math.max(mRight, right);
2682
+ if (top != null)
2683
+ mTop = Math.max(mTop, top);
2684
+ if (bottom != null)
2685
+ mBottom = Math.max(mBottom, bottom);
2686
+ }
2687
+ scrollRectIntoView(this.view.scrollDOM, {
2688
+ left: rect.left - mLeft, top: rect.top - mTop,
2689
+ right: rect.right + mRight, bottom: rect.bottom + mBottom
2690
+ }, range.head < range.anchor ? -1 : 1, center);
2691
+ }
2692
+ }
2693
+ function betweenUneditable(pos) {
2694
+ return pos.node.nodeType == 1 && pos.node.firstChild &&
2695
+ (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2696
+ (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2697
+ }
2698
+ class BlockGapWidget extends WidgetType {
2699
+ constructor(height) {
2700
+ super();
2701
+ this.height = height;
2702
+ }
2703
+ toDOM() {
2704
+ let elt = document.createElement("div");
2705
+ this.updateDOM(elt);
2706
+ return elt;
2707
+ }
2708
+ eq(other) { return other.height == this.height; }
2709
+ updateDOM(elt) {
2710
+ elt.style.height = this.height + "px";
2711
+ return true;
2712
+ }
2713
+ get estimatedHeight() { return this.height; }
2714
+ }
2715
+ function computeCompositionDeco(view, changes) {
2716
+ let sel = view.observer.selectionRange;
2717
+ let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2718
+ if (!textNode)
2719
+ return Decoration.none;
2720
+ let cView = view.docView.nearest(textNode);
2721
+ if (!cView)
2722
+ return Decoration.none;
2723
+ let from, to, topNode = textNode;
2724
+ if (cView instanceof LineView) {
2725
+ while (topNode.parentNode != cView.dom)
2726
+ topNode = topNode.parentNode;
2727
+ let prev = topNode.previousSibling;
2728
+ while (prev && !ContentView.get(prev))
2729
+ prev = prev.previousSibling;
2730
+ from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2679
2731
  }
2680
2732
  else {
2681
- for (let i = 0; i < len;) {
2682
- let start = i, rtl = types[i++] == 2 /* R */;
2683
- while (i < len && rtl == (types[i] == 2 /* R */))
2684
- i++;
2685
- order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2733
+ for (;;) {
2734
+ let { parent } = cView;
2735
+ if (!parent)
2736
+ return Decoration.none;
2737
+ if (parent instanceof LineView)
2738
+ break;
2739
+ cView = parent;
2686
2740
  }
2741
+ from = cView.posAtStart;
2742
+ to = from + cView.length;
2743
+ topNode = cView.dom;
2687
2744
  }
2688
- return order;
2745
+ let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2746
+ let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2747
+ new DOMReader([], view).readRange(topNode.firstChild, null).text;
2748
+ if (newTo - newFrom < text.length) {
2749
+ if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2750
+ newTo = newFrom + text.length;
2751
+ else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2752
+ newFrom = newTo - text.length;
2753
+ else
2754
+ return Decoration.none;
2755
+ }
2756
+ else if (state.sliceDoc(newFrom, newTo) != text) {
2757
+ return Decoration.none;
2758
+ }
2759
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2689
2760
  }
2690
- function trivialOrder(length) {
2691
- return [new BidiSpan(0, length, 0)];
2761
+ class CompositionWidget extends WidgetType {
2762
+ constructor(top, text) {
2763
+ super();
2764
+ this.top = top;
2765
+ this.text = text;
2766
+ }
2767
+ eq(other) { return this.top == other.top && this.text == other.text; }
2768
+ toDOM() { return this.top; }
2769
+ ignoreEvent() { return false; }
2770
+ get customView() { return CompositionView; }
2692
2771
  }
2693
- let movedOver = "";
2694
- function moveVisually(line, order, dir, start, forward) {
2695
- var _a;
2696
- let startIndex = start.head - line.from, spanI = -1;
2697
- if (startIndex == 0) {
2698
- if (!forward || !line.length)
2699
- return null;
2700
- if (order[0].level != dir) {
2701
- startIndex = order[0].side(false, dir);
2702
- spanI = 0;
2772
+ function nearbyTextNode(node, offset, side) {
2773
+ for (;;) {
2774
+ if (node.nodeType == 3)
2775
+ return node;
2776
+ if (node.nodeType == 1 && offset > 0 && side <= 0) {
2777
+ node = node.childNodes[offset - 1];
2778
+ offset = maxOffset(node);
2703
2779
  }
2704
- }
2705
- else if (startIndex == line.length) {
2706
- if (forward)
2780
+ else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2781
+ node = node.childNodes[offset];
2782
+ offset = 0;
2783
+ }
2784
+ else {
2707
2785
  return null;
2708
- let last = order[order.length - 1];
2709
- if (last.level != dir) {
2710
- startIndex = last.side(true, dir);
2711
- spanI = order.length - 1;
2712
2786
  }
2713
2787
  }
2714
- if (spanI < 0)
2715
- spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2716
- let span = order[spanI];
2717
- // End of span. (But not end of line--that was checked for above.)
2718
- if (startIndex == span.side(forward, dir)) {
2719
- span = order[spanI += forward ? 1 : -1];
2720
- startIndex = span.side(!forward, dir);
2788
+ }
2789
+ function nextToUneditable(node, offset) {
2790
+ if (node.nodeType != 1)
2791
+ return 0;
2792
+ return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2793
+ (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2794
+ }
2795
+ class DecorationComparator$1 {
2796
+ constructor() {
2797
+ this.changes = [];
2721
2798
  }
2722
- let indexForward = forward == (span.dir == dir);
2723
- let nextIndex = text.findClusterBreak(line.text, startIndex, indexForward);
2724
- movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2725
- if (nextIndex != span.side(forward, dir))
2726
- return state.EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2727
- let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2728
- if (!nextSpan && span.level != dir)
2729
- return state.EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2730
- if (nextSpan && nextSpan.level < span.level)
2731
- return state.EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2732
- return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2799
+ compareRange(from, to) { addRange(from, to, this.changes); }
2800
+ comparePoint(from, to) { addRange(from, to, this.changes); }
2801
+ }
2802
+ function findChangedDeco(a, b, diff) {
2803
+ let comp = new DecorationComparator$1;
2804
+ rangeset.RangeSet.compare(a, b, diff, comp);
2805
+ return comp.changes;
2806
+ }
2807
+ function inUneditable(node, inside) {
2808
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2809
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2810
+ return true;
2811
+ }
2812
+ }
2813
+ return false;
2733
2814
  }
2734
2815
 
2735
2816
  function groupAt(state$1, pos, bias = 1) {
@@ -2867,21 +2948,29 @@ function domPosInText(node, x, y) {
2867
2948
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2868
2949
  var _a;
2869
2950
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2870
- let halfLine = view.defaultLineHeight / 2;
2871
- let block, yOffset = y - docTop;
2872
- for (let bounced = false;;) {
2951
+ let block, yOffset = y - docTop, { docHeight } = view.viewState;
2952
+ if (yOffset < 0 || yOffset > docHeight) {
2953
+ if (precise)
2954
+ return null;
2955
+ yOffset = yOffset < 0 ? 0 : docHeight;
2956
+ }
2957
+ // Scan for a text block near the queried y position
2958
+ for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
2873
2959
  block = view.elementAtHeight(yOffset);
2874
- if (block.top > yOffset || block.bottom < yOffset) {
2875
- bias = block.top > yOffset ? -1 : 1;
2876
- yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2960
+ if (block.type == exports.BlockType.Text)
2961
+ break;
2962
+ for (;;) {
2963
+ // Move the y position out of this block
2964
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2965
+ if (yOffset >= 0 && yOffset <= docHeight)
2966
+ break;
2967
+ // If the document consists entirely of replaced widgets, we
2968
+ // won't find a text block, so return 0
2877
2969
  if (bounced)
2878
2970
  return precise ? null : 0;
2879
- else
2880
- bounced = true;
2971
+ bounced = true;
2972
+ bias = -bias;
2881
2973
  }
2882
- if (block.type == exports.BlockType.Text)
2883
- break;
2884
- yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2885
2974
  }
2886
2975
  y = docTop + yOffset;
2887
2976
  let lineStart = block.from;
@@ -3039,14 +3128,6 @@ class InputState {
3039
3128
  constructor(view) {
3040
3129
  this.lastKeyCode = 0;
3041
3130
  this.lastKeyTime = 0;
3042
- // On Chrome Android, backspace near widgets is just completely
3043
- // broken, and there are no key events, so we need to handle the
3044
- // beforeinput event. Deleting stuff will often create a flurry of
3045
- // events, and interrupting it before it is done just makes
3046
- // subsequent events even more broken, so again, we hold off doing
3047
- // anything until the browser is finished with whatever it is trying
3048
- // to do.
3049
- this.pendingAndroidKey = undefined;
3050
3131
  // On iOS, some keys need to have their default behavior happen
3051
3132
  // (after which we retroactively handle them and reset the DOM) to
3052
3133
  // avoid messing up the virtual keyboard state.
@@ -3115,22 +3196,15 @@ class InputState {
3115
3196
  }
3116
3197
  runCustomHandlers(type, view, event) {
3117
3198
  for (let set of this.customHandlers) {
3118
- let handler = set.handlers[type], handled = false;
3199
+ let handler = set.handlers[type];
3119
3200
  if (handler) {
3120
3201
  try {
3121
- handled = handler.call(set.plugin, event, view);
3202
+ if (handler.call(set.plugin, event, view))
3203
+ return true;
3122
3204
  }
3123
3205
  catch (e) {
3124
3206
  logException(view.state, e);
3125
3207
  }
3126
- if (handled || event.defaultPrevented) {
3127
- // Chrome for Android often applies a bunch of nonsensical
3128
- // DOM changes after an enter press, even when
3129
- // preventDefault-ed. This tries to ignore those.
3130
- if (browser.android && type == "keydown" && event.keyCode == 13)
3131
- view.observer.flushSoon();
3132
- return true;
3133
- }
3134
3208
  }
3135
3209
  }
3136
3210
  return false;
@@ -3154,6 +3228,16 @@ class InputState {
3154
3228
  this.lastKeyTime = Date.now();
3155
3229
  if (this.screenKeyEvent(view, event))
3156
3230
  return true;
3231
+ // Chrome for Android usually doesn't fire proper key events, but
3232
+ // occasionally does, usually surrounded by a bunch of complicated
3233
+ // composition changes. When an enter or backspace key event is
3234
+ // seen, hold off on handling DOM events for a bit, and then
3235
+ // dispatch it.
3236
+ if (browser.android && browser.chrome && !event.synthetic &&
3237
+ (event.keyCode == 13 || event.keyCode == 8)) {
3238
+ view.observer.delayAndroidKey(event.key, event.keyCode);
3239
+ return true;
3240
+ }
3157
3241
  // Prevent the default behavior of Enter on iOS makes the
3158
3242
  // virtual keyboard get stuck in the wrong (lowercase)
3159
3243
  // state. So we let it go through, and then, in
@@ -3175,24 +3259,6 @@ class InputState {
3175
3259
  this.pendingIOSKey = undefined;
3176
3260
  return dispatchKey(view.contentDOM, key.key, key.keyCode);
3177
3261
  }
3178
- // This causes the DOM observer to pause for a bit, and sets an
3179
- // animation frame (which seems the most reliable way to detect
3180
- // 'Chrome is done flailing about messing with the DOM') to fire a
3181
- // fake key event and re-sync the view again.
3182
- setPendingAndroidKey(view, pending) {
3183
- this.pendingAndroidKey = pending;
3184
- requestAnimationFrame(() => {
3185
- let key = this.pendingAndroidKey;
3186
- if (!key)
3187
- return;
3188
- this.pendingAndroidKey = undefined;
3189
- view.observer.processRecords();
3190
- let startState = view.state;
3191
- dispatchKey(view.contentDOM, key.key, key.keyCode);
3192
- if (view.state == startState)
3193
- view.docView.reset(true);
3194
- });
3195
- }
3196
3262
  ignoreDuringComposition(event) {
3197
3263
  if (!/^key/.test(event.type))
3198
3264
  return false;
@@ -3222,10 +3288,10 @@ class InputState {
3222
3288
  return (event.type == "keydown" && event.keyCode != 229) ||
3223
3289
  event.type == "compositionend" && !browser.ios;
3224
3290
  }
3225
- startMouseSelection(view, event, style) {
3291
+ startMouseSelection(mouseSelection) {
3226
3292
  if (this.mouseSelection)
3227
3293
  this.mouseSelection.destroy();
3228
- this.mouseSelection = new MouseSelection(this, view, event, style);
3294
+ this.mouseSelection = mouseSelection;
3229
3295
  }
3230
3296
  update(update) {
3231
3297
  if (this.mouseSelection)
@@ -3246,10 +3312,10 @@ const PendingKeys = [
3246
3312
  // Key codes for modifier keys
3247
3313
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3248
3314
  class MouseSelection {
3249
- constructor(inputState, view, startEvent, style) {
3250
- this.inputState = inputState;
3315
+ constructor(view, startEvent, style, mustSelect) {
3251
3316
  this.view = view;
3252
3317
  this.style = style;
3318
+ this.mustSelect = mustSelect;
3253
3319
  this.lastEvent = startEvent;
3254
3320
  let doc = view.contentDOM.ownerDocument;
3255
3321
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
@@ -3283,16 +3349,18 @@ class MouseSelection {
3283
3349
  let doc = this.view.contentDOM.ownerDocument;
3284
3350
  doc.removeEventListener("mousemove", this.move);
3285
3351
  doc.removeEventListener("mouseup", this.up);
3286
- this.inputState.mouseSelection = null;
3352
+ this.view.inputState.mouseSelection = null;
3287
3353
  }
3288
3354
  select(event) {
3289
3355
  let selection = this.style.get(event, this.extend, this.multiple);
3290
- if (!selection.eq(this.view.state.selection) || selection.main.assoc != this.view.state.selection.main.assoc)
3356
+ if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3357
+ selection.main.assoc != this.view.state.selection.main.assoc)
3291
3358
  this.view.dispatch({
3292
3359
  selection,
3293
3360
  userEvent: "select.pointer",
3294
3361
  scrollIntoView: true
3295
3362
  });
3363
+ this.mustSelect = false;
3296
3364
  }
3297
3365
  update(update) {
3298
3366
  if (update.docChanged && this.dragging)
@@ -3411,9 +3479,10 @@ handlers.mousedown = (view, event) => {
3411
3479
  if (!style && event.button == 0)
3412
3480
  style = basicMouseSelection(view, event);
3413
3481
  if (style) {
3414
- if (view.root.activeElement != view.contentDOM)
3482
+ let mustFocus = view.root.activeElement != view.contentDOM;
3483
+ if (mustFocus)
3415
3484
  view.observer.ignore(() => focusPreventScroll(view.contentDOM));
3416
- view.inputState.startMouseSelection(view, event, style);
3485
+ view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
3417
3486
  }
3418
3487
  };
3419
3488
  function rangeForClick(view, pos, bias, type) {
@@ -3668,12 +3737,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
3668
3737
  if (view.inputState.compositionFirstChange == null)
3669
3738
  view.inputState.compositionFirstChange = true;
3670
3739
  if (view.inputState.composing < 0) {
3740
+ // FIXME possibly set a timeout to clear it again on Android
3741
+ view.inputState.composing = 0;
3671
3742
  if (view.docView.compositionDeco.size) {
3672
3743
  view.observer.flush();
3673
3744
  forceClearComposition(view, true);
3674
3745
  }
3675
- // FIXME possibly set a timeout to clear it again on Android
3676
- view.inputState.composing = 0;
3677
3746
  }
3678
3747
  };
3679
3748
  handlers.compositionend = view => {
@@ -3699,7 +3768,7 @@ handlers.beforeinput = (view, event) => {
3699
3768
  // seems to do nothing at all on Chrome).
3700
3769
  let pending;
3701
3770
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3702
- view.inputState.setPendingAndroidKey(view, pending);
3771
+ view.observer.delayAndroidKey(pending.key, pending.keyCode);
3703
3772
  if (pending.key == "Backspace" || pending.key == "Delete") {
3704
3773
  let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3705
3774
  setTimeout(() => {
@@ -4129,12 +4198,12 @@ class HeightMapBranch extends HeightMap {
4129
4198
  get break() { return this.flags & 1 /* Break */; }
4130
4199
  blockAt(height, doc, top, offset) {
4131
4200
  let mid = top + this.left.height;
4132
- return height < mid || this.right.height == 0 ? this.left.blockAt(height, doc, top, offset)
4201
+ return height < mid ? this.left.blockAt(height, doc, top, offset)
4133
4202
  : this.right.blockAt(height, doc, mid, offset + this.left.length + this.break);
4134
4203
  }
4135
4204
  lineAt(value, type, doc, top, offset) {
4136
4205
  let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
4137
- let left = type == QueryType.ByHeight ? value < rightTop || this.right.height == 0 : value < rightOffset;
4206
+ let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
4138
4207
  let base = left ? this.left.lineAt(value, type, doc, top, offset)
4139
4208
  : this.right.lineAt(value, type, doc, rightTop, rightOffset);
4140
4209
  if (this.break || (left ? base.to < rightOffset : base.from > rightOffset))
@@ -4275,7 +4344,9 @@ class NodeBuilder {
4275
4344
  }
4276
4345
  point(from, to, deco) {
4277
4346
  if (from < to || deco.heightRelevant) {
4278
- let height = deco.widget ? Math.max(0, deco.widget.estimatedHeight) : 0;
4347
+ let height = deco.widget ? deco.widget.estimatedHeight : 0;
4348
+ if (height < 0)
4349
+ height = this.oracle.lineHeight;
4279
4350
  let len = to - from;
4280
4351
  if (deco.block) {
4281
4352
  this.addBlock(new HeightMapBlock(len, height, deco.type));
@@ -4403,8 +4474,8 @@ function visiblePixelRange(dom, paddingTop) {
4403
4474
  break;
4404
4475
  }
4405
4476
  }
4406
- return { left: left - rect.left, right: right - rect.left,
4407
- top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
4477
+ return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4478
+ top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4408
4479
  }
4409
4480
  // Line gaps are placeholder widgets used to hide pieces of overlong
4410
4481
  // lines within the viewport, as a kludge to keep the editor
@@ -4631,7 +4702,7 @@ class ViewState {
4631
4702
  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);
4632
4703
  // If scrollTarget is given, make sure the viewport includes that position
4633
4704
  if (scrollTarget) {
4634
- let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4705
+ let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4635
4706
  if (head < viewport.from || head > viewport.to) {
4636
4707
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4637
4708
  if (scrollTarget.center)
@@ -4652,6 +4723,8 @@ class ViewState {
4652
4723
  // Checks if a given viewport covers the visible part of the
4653
4724
  // document and not too much beyond that.
4654
4725
  viewportIsAppropriate({ from, to }, bias = 0) {
4726
+ if (!this.inView)
4727
+ return true;
4655
4728
  let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4656
4729
  let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4657
4730
  let { visibleTop, visibleBottom } = this;
@@ -4758,8 +4831,11 @@ class ViewState {
4758
4831
  elementAtHeight(height) {
4759
4832
  return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4760
4833
  }
4834
+ get docHeight() {
4835
+ return this.scaler.toDOM(this.heightMap.height);
4836
+ }
4761
4837
  get contentHeight() {
4762
- return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4838
+ return this.docHeight + this.paddingTop + this.paddingBottom;
4763
4839
  }
4764
4840
  }
4765
4841
  class Viewport {
@@ -5013,7 +5089,8 @@ const baseTheme = buildTheme("." + baseThemeID, {
5013
5089
  },
5014
5090
  ".cm-placeholder": {
5015
5091
  color: "#888",
5016
- display: "inline-block"
5092
+ display: "inline-block",
5093
+ verticalAlign: "top",
5017
5094
  },
5018
5095
  ".cm-button": {
5019
5096
  verticalAlign: "middle",
@@ -5080,6 +5157,7 @@ class DOMObserver {
5080
5157
  this.delayedFlush = -1;
5081
5158
  this.resizeTimeout = -1;
5082
5159
  this.queue = [];
5160
+ this.delayedAndroidKey = null;
5083
5161
  this.scrollTargets = [];
5084
5162
  this.intersection = null;
5085
5163
  this.resize = null;
@@ -5163,7 +5241,7 @@ class DOMObserver {
5163
5241
  }
5164
5242
  }
5165
5243
  onSelectionChange(event) {
5166
- if (!this.readSelectionRange())
5244
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5167
5245
  return;
5168
5246
  let { view } = this, sel = this.selectionRange;
5169
5247
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5259,6 +5337,32 @@ class DOMObserver {
5259
5337
  this.queue.length = 0;
5260
5338
  this.selectionChanged = false;
5261
5339
  }
5340
+ // Chrome Android, especially in combination with GBoard, not only
5341
+ // doesn't reliably fire regular key events, but also often
5342
+ // surrounds the effect of enter or backspace with a bunch of
5343
+ // composition events that, when interrupted, cause text duplication
5344
+ // or other kinds of corruption. This hack makes the editor back off
5345
+ // from handling DOM changes for a moment when such a key is
5346
+ // detected (via beforeinput or keydown), and then dispatches the
5347
+ // key event, throwing away the DOM changes if it gets handled.
5348
+ delayAndroidKey(key, keyCode) {
5349
+ if (!this.delayedAndroidKey)
5350
+ requestAnimationFrame(() => {
5351
+ let key = this.delayedAndroidKey;
5352
+ this.delayedAndroidKey = null;
5353
+ let startState = this.view.state;
5354
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5355
+ this.processRecords();
5356
+ else
5357
+ this.flush();
5358
+ if (this.view.state == startState)
5359
+ this.view.update([]);
5360
+ });
5361
+ // Since backspace beforeinput is sometimes signalled spuriously,
5362
+ // Enter always takes precedence.
5363
+ if (!this.delayedAndroidKey || key == "Enter")
5364
+ this.delayedAndroidKey = { key, keyCode };
5365
+ }
5262
5366
  flushSoon() {
5263
5367
  if (this.delayedFlush < 0)
5264
5368
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5295,13 +5399,13 @@ class DOMObserver {
5295
5399
  }
5296
5400
  // Apply pending changes, if any
5297
5401
  flush(readSelection = true) {
5298
- if (readSelection)
5299
- this.readSelectionRange();
5300
5402
  // Completely hold off flushing when pending keys are set—the code
5301
5403
  // managing those will make sure processRecords is called and the
5302
5404
  // view is resynchronized after
5303
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5405
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5304
5406
  return;
5407
+ if (readSelection)
5408
+ this.readSelectionRange();
5305
5409
  let { from, to, typeOver } = this.processRecords();
5306
5410
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5307
5411
  if (from < 0 && !newSel)
@@ -5311,7 +5415,7 @@ class DOMObserver {
5311
5415
  this.onChange(from, to, typeOver);
5312
5416
  // The view wasn't updated
5313
5417
  if (this.view.state == startState)
5314
- this.view.docView.reset(newSel);
5418
+ this.view.update([]);
5315
5419
  }
5316
5420
  readMutation(rec) {
5317
5421
  let cView = this.view.docView.nearest(rec.target);
@@ -5442,11 +5546,22 @@ function applyDOMChange(view, start, end, typeOver) {
5442
5546
  };
5443
5547
  if (change) {
5444
5548
  let startState = view.state;
5549
+ if (browser.ios && view.inputState.flushIOSKey(view))
5550
+ return;
5445
5551
  // Android browsers don't fire reasonable key events for enter,
5446
5552
  // backspace, or delete. So this detects changes that look like
5447
5553
  // they're caused by those keys, and reinterprets them as key
5448
- // events.
5449
- if (browser.ios && view.inputState.flushIOSKey(view))
5554
+ // events. (Some of these keys are also handled by beforeinput
5555
+ // events and the pendingAndroidKey mechanism, but that's not
5556
+ // reliable in all situations.)
5557
+ if (browser.android &&
5558
+ ((change.from == sel.from && change.to == sel.to &&
5559
+ change.insert.length == 1 && change.insert.lines == 2 &&
5560
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
5561
+ (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5562
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
5563
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5564
+ dispatchKey(view.contentDOM, "Delete", 46))))
5450
5565
  return;
5451
5566
  let text = change.insert.toString();
5452
5567
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
@@ -5519,76 +5634,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
5519
5634
  }
5520
5635
  return { from, toA, toB };
5521
5636
  }
5522
- class DOMReader {
5523
- constructor(points, view) {
5524
- this.points = points;
5525
- this.view = view;
5526
- this.text = "";
5527
- this.lineBreak = view.state.lineBreak;
5528
- }
5529
- readRange(start, end) {
5530
- if (!start)
5531
- return;
5532
- let parent = start.parentNode;
5533
- for (let cur = start;;) {
5534
- this.findPointBefore(parent, cur);
5535
- this.readNode(cur);
5536
- let next = cur.nextSibling;
5537
- if (next == end)
5538
- break;
5539
- let view = ContentView.get(cur), nextView = ContentView.get(next);
5540
- if (view && nextView ? view.breakAfter :
5541
- (view ? view.breakAfter : isBlockElement(cur)) ||
5542
- (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5543
- this.text += this.lineBreak;
5544
- cur = next;
5545
- }
5546
- this.findPointBefore(parent, end);
5547
- }
5548
- readNode(node) {
5549
- if (node.cmIgnore)
5550
- return;
5551
- let view = ContentView.get(node);
5552
- let fromView = view && view.overrideDOMText;
5553
- let text;
5554
- if (fromView != null)
5555
- text = fromView.sliceString(0, undefined, this.lineBreak);
5556
- else if (node.nodeType == 3)
5557
- text = node.nodeValue;
5558
- else if (node.nodeName == "BR")
5559
- text = node.nextSibling ? this.lineBreak : "";
5560
- else if (node.nodeType == 1)
5561
- this.readRange(node.firstChild, null);
5562
- if (text != null) {
5563
- this.findPointIn(node, text.length);
5564
- this.text += text;
5565
- // Chrome inserts two newlines when pressing shift-enter at the
5566
- // end of a line. This drops one of those.
5567
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
5568
- this.text = this.text.slice(0, -1);
5569
- }
5570
- }
5571
- findPointBefore(node, next) {
5572
- for (let point of this.points)
5573
- if (point.node == node && node.childNodes[point.offset] == next)
5574
- point.pos = this.text.length;
5575
- }
5576
- findPointIn(node, maxLen) {
5577
- for (let point of this.points)
5578
- if (point.node == node)
5579
- point.pos = this.text.length + Math.min(point.offset, maxLen);
5580
- }
5581
- }
5582
- function isBlockElement(node) {
5583
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
5584
- }
5585
- class DOMPoint {
5586
- constructor(node, offset) {
5587
- this.node = node;
5588
- this.offset = offset;
5589
- this.pos = -1;
5590
- }
5591
- }
5592
5637
  function selectionPoints(view) {
5593
5638
  let result = [];
5594
5639
  if (view.root.activeElement != view.contentDOM)
@@ -5671,7 +5716,9 @@ class EditorView {
5671
5716
  this.dispatch = this.dispatch.bind(this);
5672
5717
  this.root = (config.root || getRoot(config.parent) || document);
5673
5718
  this.viewState = new ViewState(config.state || state.EditorState.create());
5674
- this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5719
+ this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
5720
+ for (let plugin of this.plugins)
5721
+ plugin.update(this);
5675
5722
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
5676
5723
  applyDOMChange(this, from, to, typeOver);
5677
5724
  }, event => {
@@ -5808,8 +5855,10 @@ class EditorView {
5808
5855
  for (let plugin of this.plugins)
5809
5856
  plugin.destroy(this);
5810
5857
  this.viewState = new ViewState(newState);
5811
- this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5858
+ this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec));
5812
5859
  this.pluginMap.clear();
5860
+ for (let plugin of this.plugins)
5861
+ plugin.update(this);
5813
5862
  this.docView = new DocView(this);
5814
5863
  this.inputState.ensureHandlers(this);
5815
5864
  this.mountStyles();
@@ -5870,7 +5919,9 @@ class EditorView {
5870
5919
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5871
5920
  break;
5872
5921
  if (i > 5) {
5873
- console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5922
+ console.warn(this.measureRequests.length
5923
+ ? "Measure loop restarted more than 5 times"
5924
+ : "Viewport failed to stabilize");
5874
5925
  break;
5875
5926
  }
5876
5927
  let measuring = [];
@@ -5916,7 +5967,8 @@ class EditorView {
5916
5967
  }
5917
5968
  if (redrawn)
5918
5969
  this.docView.updateSelection(true);
5919
- if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5970
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
5971
+ this.measureRequests.length == 0)
5920
5972
  break;
5921
5973
  }
5922
5974
  }
@@ -6208,6 +6260,11 @@ class EditorView {
6208
6260
  Find the DOM parent node and offset (child offset if `node` is
6209
6261
  an element, character offset when it is a text node) at the
6210
6262
  given document position.
6263
+
6264
+ Note that for positions that aren't currently in
6265
+ `visibleRanges`, the resulting DOM position isn't necessarily
6266
+ meaningful (it may just point before or after a placeholder
6267
+ element).
6211
6268
  */
6212
6269
  domAtPos(pos) {
6213
6270
  return this.docView.domAtPos(pos);
@@ -6868,7 +6925,7 @@ function measureRange(view, range) {
6868
6925
  let between = [];
6869
6926
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6870
6927
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6871
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6928
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6872
6929
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6873
6930
  return pieces(top).concat(between).concat(pieces(bottom));
6874
6931
  }
@@ -6941,6 +6998,22 @@ function iterMatches(doc, re, from, to, f) {
6941
6998
  f(pos + m.index, pos + m.index + m[0].length, m);
6942
6999
  }
6943
7000
  }
7001
+ function matchRanges(view, maxLength) {
7002
+ let visible = view.visibleRanges;
7003
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
7004
+ visible[0].to == view.viewport.to)
7005
+ return visible;
7006
+ let result = [];
7007
+ for (let { from, to } of visible) {
7008
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7009
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7010
+ if (result.length && result[result.length - 1].to >= from)
7011
+ result[result.length - 1].to = to;
7012
+ else
7013
+ result.push({ from, to });
7014
+ }
7015
+ return result;
7016
+ }
6944
7017
  /**
6945
7018
  Helper class used to make it easier to maintain decorations on
6946
7019
  visible code that matches a given regular expression. To be used
@@ -6952,12 +7025,13 @@ class MatchDecorator {
6952
7025
  Create a decorator.
6953
7026
  */
6954
7027
  constructor(config) {
6955
- let { regexp, decoration, boundary } = config;
7028
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
6956
7029
  if (!regexp.global)
6957
7030
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
6958
7031
  this.regexp = regexp;
6959
7032
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
6960
7033
  this.boundary = boundary;
7034
+ this.maxLength = maxLength;
6961
7035
  }
6962
7036
  /**
6963
7037
  Compute the full set of decorations for matches in the given
@@ -6966,7 +7040,7 @@ class MatchDecorator {
6966
7040
  */
6967
7041
  createDeco(view) {
6968
7042
  let build = new rangeset.RangeSetBuilder();
6969
- for (let { from, to } of view.visibleRanges)
7043
+ for (let { from, to } of matchRanges(view, this.maxLength))
6970
7044
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
6971
7045
  return build.finish();
6972
7046
  }
@@ -7269,9 +7343,7 @@ const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRan
7269
7343
 
7270
7344
  Object.defineProperty(exports, 'Range', {
7271
7345
  enumerable: true,
7272
- get: function () {
7273
- return rangeset.Range;
7274
- }
7346
+ get: function () { return rangeset.Range; }
7275
7347
  });
7276
7348
  exports.BidiSpan = BidiSpan;
7277
7349
  exports.BlockInfo = BlockInfo;