@codemirror/view 0.19.27 → 0.19.31

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);
2554
2519
  }
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 */;
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");
2539
+ }
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(() => {
@@ -4405,8 +4474,8 @@ function visiblePixelRange(dom, paddingTop) {
4405
4474
  break;
4406
4475
  }
4407
4476
  }
4408
- return { left: left - rect.left, right: right - rect.left,
4409
- 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) };
4410
4479
  }
4411
4480
  // Line gaps are placeholder widgets used to hide pieces of overlong
4412
4481
  // lines within the viewport, as a kludge to keep the editor
@@ -4633,7 +4702,7 @@ class ViewState {
4633
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);
4634
4703
  // If scrollTarget is given, make sure the viewport includes that position
4635
4704
  if (scrollTarget) {
4636
- let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4705
+ let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4637
4706
  if (head < viewport.from || head > viewport.to) {
4638
4707
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4639
4708
  if (scrollTarget.center)
@@ -4654,6 +4723,8 @@ class ViewState {
4654
4723
  // Checks if a given viewport covers the visible part of the
4655
4724
  // document and not too much beyond that.
4656
4725
  viewportIsAppropriate({ from, to }, bias = 0) {
4726
+ if (!this.inView)
4727
+ return true;
4657
4728
  let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4658
4729
  let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4659
4730
  let { visibleTop, visibleBottom } = this;
@@ -4760,8 +4831,11 @@ class ViewState {
4760
4831
  elementAtHeight(height) {
4761
4832
  return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4762
4833
  }
4834
+ get docHeight() {
4835
+ return this.scaler.toDOM(this.heightMap.height);
4836
+ }
4763
4837
  get contentHeight() {
4764
- return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4838
+ return this.docHeight + this.paddingTop + this.paddingBottom;
4765
4839
  }
4766
4840
  }
4767
4841
  class Viewport {
@@ -4991,11 +5065,13 @@ const baseTheme = buildTheme("." + baseThemeID, {
4991
5065
  // recomputation.
4992
5066
  "@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
4993
5067
  "@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
4994
- ".cm-cursor": {
5068
+ ".cm-cursor, .cm-dropCursor": {
4995
5069
  position: "absolute",
4996
5070
  borderLeft: "1.2px solid black",
4997
5071
  marginLeft: "-0.6px",
4998
5072
  pointerEvents: "none",
5073
+ },
5074
+ ".cm-cursor": {
4999
5075
  display: "none"
5000
5076
  },
5001
5077
  "&dark .cm-cursor": {
@@ -5015,7 +5091,8 @@ const baseTheme = buildTheme("." + baseThemeID, {
5015
5091
  },
5016
5092
  ".cm-placeholder": {
5017
5093
  color: "#888",
5018
- display: "inline-block"
5094
+ display: "inline-block",
5095
+ verticalAlign: "top",
5019
5096
  },
5020
5097
  ".cm-button": {
5021
5098
  verticalAlign: "middle",
@@ -5082,6 +5159,7 @@ class DOMObserver {
5082
5159
  this.delayedFlush = -1;
5083
5160
  this.resizeTimeout = -1;
5084
5161
  this.queue = [];
5162
+ this.delayedAndroidKey = null;
5085
5163
  this.scrollTargets = [];
5086
5164
  this.intersection = null;
5087
5165
  this.resize = null;
@@ -5165,7 +5243,7 @@ class DOMObserver {
5165
5243
  }
5166
5244
  }
5167
5245
  onSelectionChange(event) {
5168
- if (!this.readSelectionRange())
5246
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5169
5247
  return;
5170
5248
  let { view } = this, sel = this.selectionRange;
5171
5249
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5261,6 +5339,32 @@ class DOMObserver {
5261
5339
  this.queue.length = 0;
5262
5340
  this.selectionChanged = false;
5263
5341
  }
5342
+ // Chrome Android, especially in combination with GBoard, not only
5343
+ // doesn't reliably fire regular key events, but also often
5344
+ // surrounds the effect of enter or backspace with a bunch of
5345
+ // composition events that, when interrupted, cause text duplication
5346
+ // or other kinds of corruption. This hack makes the editor back off
5347
+ // from handling DOM changes for a moment when such a key is
5348
+ // detected (via beforeinput or keydown), and then dispatches the
5349
+ // key event, throwing away the DOM changes if it gets handled.
5350
+ delayAndroidKey(key, keyCode) {
5351
+ if (!this.delayedAndroidKey)
5352
+ requestAnimationFrame(() => {
5353
+ let key = this.delayedAndroidKey;
5354
+ this.delayedAndroidKey = null;
5355
+ let startState = this.view.state;
5356
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5357
+ this.processRecords();
5358
+ else
5359
+ this.flush();
5360
+ if (this.view.state == startState)
5361
+ this.view.update([]);
5362
+ });
5363
+ // Since backspace beforeinput is sometimes signalled spuriously,
5364
+ // Enter always takes precedence.
5365
+ if (!this.delayedAndroidKey || key == "Enter")
5366
+ this.delayedAndroidKey = { key, keyCode };
5367
+ }
5264
5368
  flushSoon() {
5265
5369
  if (this.delayedFlush < 0)
5266
5370
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5297,13 +5401,13 @@ class DOMObserver {
5297
5401
  }
5298
5402
  // Apply pending changes, if any
5299
5403
  flush(readSelection = true) {
5300
- if (readSelection)
5301
- this.readSelectionRange();
5302
5404
  // Completely hold off flushing when pending keys are set—the code
5303
5405
  // managing those will make sure processRecords is called and the
5304
5406
  // view is resynchronized after
5305
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5407
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5306
5408
  return;
5409
+ if (readSelection)
5410
+ this.readSelectionRange();
5307
5411
  let { from, to, typeOver } = this.processRecords();
5308
5412
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5309
5413
  if (from < 0 && !newSel)
@@ -5313,7 +5417,7 @@ class DOMObserver {
5313
5417
  this.onChange(from, to, typeOver);
5314
5418
  // The view wasn't updated
5315
5419
  if (this.view.state == startState)
5316
- this.view.docView.reset(newSel);
5420
+ this.view.update([]);
5317
5421
  }
5318
5422
  readMutation(rec) {
5319
5423
  let cView = this.view.docView.nearest(rec.target);
@@ -5444,11 +5548,22 @@ function applyDOMChange(view, start, end, typeOver) {
5444
5548
  };
5445
5549
  if (change) {
5446
5550
  let startState = view.state;
5551
+ if (browser.ios && view.inputState.flushIOSKey(view))
5552
+ return;
5447
5553
  // Android browsers don't fire reasonable key events for enter,
5448
5554
  // backspace, or delete. So this detects changes that look like
5449
5555
  // they're caused by those keys, and reinterprets them as key
5450
- // events.
5451
- if (browser.ios && view.inputState.flushIOSKey(view))
5556
+ // events. (Some of these keys are also handled by beforeinput
5557
+ // events and the pendingAndroidKey mechanism, but that's not
5558
+ // reliable in all situations.)
5559
+ if (browser.android &&
5560
+ ((change.from == sel.from && change.to == sel.to &&
5561
+ change.insert.length == 1 && change.insert.lines == 2 &&
5562
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
5563
+ (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5564
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
5565
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5566
+ dispatchKey(view.contentDOM, "Delete", 46))))
5452
5567
  return;
5453
5568
  let text = change.insert.toString();
5454
5569
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
@@ -5521,76 +5636,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
5521
5636
  }
5522
5637
  return { from, toA, toB };
5523
5638
  }
5524
- class DOMReader {
5525
- constructor(points, view) {
5526
- this.points = points;
5527
- this.view = view;
5528
- this.text = "";
5529
- this.lineBreak = view.state.lineBreak;
5530
- }
5531
- readRange(start, end) {
5532
- if (!start)
5533
- return;
5534
- let parent = start.parentNode;
5535
- for (let cur = start;;) {
5536
- this.findPointBefore(parent, cur);
5537
- this.readNode(cur);
5538
- let next = cur.nextSibling;
5539
- if (next == end)
5540
- break;
5541
- let view = ContentView.get(cur), nextView = ContentView.get(next);
5542
- if (view && nextView ? view.breakAfter :
5543
- (view ? view.breakAfter : isBlockElement(cur)) ||
5544
- (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5545
- this.text += this.lineBreak;
5546
- cur = next;
5547
- }
5548
- this.findPointBefore(parent, end);
5549
- }
5550
- readNode(node) {
5551
- if (node.cmIgnore)
5552
- return;
5553
- let view = ContentView.get(node);
5554
- let fromView = view && view.overrideDOMText;
5555
- let text;
5556
- if (fromView != null)
5557
- text = fromView.sliceString(0, undefined, this.lineBreak);
5558
- else if (node.nodeType == 3)
5559
- text = node.nodeValue;
5560
- else if (node.nodeName == "BR")
5561
- text = node.nextSibling ? this.lineBreak : "";
5562
- else if (node.nodeType == 1)
5563
- this.readRange(node.firstChild, null);
5564
- if (text != null) {
5565
- this.findPointIn(node, text.length);
5566
- this.text += text;
5567
- // Chrome inserts two newlines when pressing shift-enter at the
5568
- // end of a line. This drops one of those.
5569
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
5570
- this.text = this.text.slice(0, -1);
5571
- }
5572
- }
5573
- findPointBefore(node, next) {
5574
- for (let point of this.points)
5575
- if (point.node == node && node.childNodes[point.offset] == next)
5576
- point.pos = this.text.length;
5577
- }
5578
- findPointIn(node, maxLen) {
5579
- for (let point of this.points)
5580
- if (point.node == node)
5581
- point.pos = this.text.length + Math.min(point.offset, maxLen);
5582
- }
5583
- }
5584
- function isBlockElement(node) {
5585
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
5586
- }
5587
- class DOMPoint {
5588
- constructor(node, offset) {
5589
- this.node = node;
5590
- this.offset = offset;
5591
- this.pos = -1;
5592
- }
5593
- }
5594
5639
  function selectionPoints(view) {
5595
5640
  let result = [];
5596
5641
  if (view.root.activeElement != view.contentDOM)
@@ -5876,7 +5921,9 @@ class EditorView {
5876
5921
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5877
5922
  break;
5878
5923
  if (i > 5) {
5879
- console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5924
+ console.warn(this.measureRequests.length
5925
+ ? "Measure loop restarted more than 5 times"
5926
+ : "Viewport failed to stabilize");
5880
5927
  break;
5881
5928
  }
5882
5929
  let measuring = [];
@@ -5922,7 +5969,8 @@ class EditorView {
5922
5969
  }
5923
5970
  if (redrawn)
5924
5971
  this.docView.updateSelection(true);
5925
- if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5972
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
5973
+ this.measureRequests.length == 0)
5926
5974
  break;
5927
5975
  }
5928
5976
  }
@@ -6214,6 +6262,11 @@ class EditorView {
6214
6262
  Find the DOM parent node and offset (child offset if `node` is
6215
6263
  an element, character offset when it is a text node) at the
6216
6264
  given document position.
6265
+
6266
+ Note that for positions that aren't currently in
6267
+ `visibleRanges`, the resulting DOM position isn't necessarily
6268
+ meaningful (it may just point before or after a placeholder
6269
+ element).
6217
6270
  */
6218
6271
  domAtPos(pos) {
6219
6272
  return this.docView.domAtPos(pos);
@@ -6874,7 +6927,7 @@ function measureRange(view, range) {
6874
6927
  let between = [];
6875
6928
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6876
6929
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6877
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6930
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6878
6931
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6879
6932
  return pieces(top).concat(between).concat(pieces(bottom));
6880
6933
  }
@@ -6939,6 +6992,94 @@ function measureCursor(view, cursor, primary) {
6939
6992
  return new Piece(pos.left - base.left, pos.top - base.top, -1, pos.bottom - pos.top, primary ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary");
6940
6993
  }
6941
6994
 
6995
+ const setDropCursorPos = state.StateEffect.define({
6996
+ map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
6997
+ });
6998
+ const dropCursorPos = state.StateField.define({
6999
+ create() { return null; },
7000
+ update(pos, tr) {
7001
+ if (pos != null)
7002
+ pos = tr.changes.mapPos(pos);
7003
+ return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
7004
+ }
7005
+ });
7006
+ const drawDropCursor = ViewPlugin.fromClass(class {
7007
+ constructor(view) {
7008
+ this.view = view;
7009
+ this.cursor = null;
7010
+ this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
7011
+ }
7012
+ update(update) {
7013
+ var _a;
7014
+ let cursorPos = update.state.field(dropCursorPos);
7015
+ if (cursorPos == null) {
7016
+ if (this.cursor != null) {
7017
+ (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
7018
+ this.cursor = null;
7019
+ }
7020
+ }
7021
+ else {
7022
+ if (!this.cursor) {
7023
+ this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
7024
+ this.cursor.className = "cm-dropCursor";
7025
+ }
7026
+ if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
7027
+ this.view.requestMeasure(this.measureReq);
7028
+ }
7029
+ }
7030
+ readPos() {
7031
+ let pos = this.view.state.field(dropCursorPos);
7032
+ let rect = pos != null && this.view.coordsAtPos(pos);
7033
+ if (!rect)
7034
+ return null;
7035
+ let outer = this.view.scrollDOM.getBoundingClientRect();
7036
+ return { left: rect.left - outer.left, top: rect.top - outer.top, height: rect.bottom - rect.top };
7037
+ }
7038
+ drawCursor(pos) {
7039
+ if (this.cursor) {
7040
+ if (pos) {
7041
+ this.cursor.style.left = pos.left + "px";
7042
+ this.cursor.style.top = pos.top + "px";
7043
+ this.cursor.style.height = pos.height + "px";
7044
+ }
7045
+ else {
7046
+ this.cursor.style.left = "-100000px";
7047
+ }
7048
+ }
7049
+ }
7050
+ destroy() {
7051
+ if (this.cursor)
7052
+ this.cursor.remove();
7053
+ }
7054
+ setDropPos(pos) {
7055
+ if (this.view.state.field(dropCursorPos) != pos)
7056
+ this.view.dispatch({ effects: setDropCursorPos.of(pos) });
7057
+ }
7058
+ }, {
7059
+ eventHandlers: {
7060
+ dragover(event) {
7061
+ this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
7062
+ },
7063
+ dragleave(event) {
7064
+ if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
7065
+ this.setDropPos(null);
7066
+ },
7067
+ dragend() {
7068
+ this.setDropPos(null);
7069
+ },
7070
+ drop() {
7071
+ this.setDropPos(null);
7072
+ }
7073
+ }
7074
+ });
7075
+ /**
7076
+ Draws a cursor at the current drop position when something is
7077
+ dragged over the editor.
7078
+ */
7079
+ function dropCursor() {
7080
+ return [dropCursorPos, drawDropCursor];
7081
+ }
7082
+
6942
7083
  function iterMatches(doc, re, from, to, f) {
6943
7084
  re.lastIndex = 0;
6944
7085
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
@@ -6947,6 +7088,22 @@ function iterMatches(doc, re, from, to, f) {
6947
7088
  f(pos + m.index, pos + m.index + m[0].length, m);
6948
7089
  }
6949
7090
  }
7091
+ function matchRanges(view, maxLength) {
7092
+ let visible = view.visibleRanges;
7093
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
7094
+ visible[0].to == view.viewport.to)
7095
+ return visible;
7096
+ let result = [];
7097
+ for (let { from, to } of visible) {
7098
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7099
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7100
+ if (result.length && result[result.length - 1].to >= from)
7101
+ result[result.length - 1].to = to;
7102
+ else
7103
+ result.push({ from, to });
7104
+ }
7105
+ return result;
7106
+ }
6950
7107
  /**
6951
7108
  Helper class used to make it easier to maintain decorations on
6952
7109
  visible code that matches a given regular expression. To be used
@@ -6958,12 +7115,13 @@ class MatchDecorator {
6958
7115
  Create a decorator.
6959
7116
  */
6960
7117
  constructor(config) {
6961
- let { regexp, decoration, boundary } = config;
7118
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
6962
7119
  if (!regexp.global)
6963
7120
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
6964
7121
  this.regexp = regexp;
6965
7122
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
6966
7123
  this.boundary = boundary;
7124
+ this.maxLength = maxLength;
6967
7125
  }
6968
7126
  /**
6969
7127
  Compute the full set of decorations for matches in the given
@@ -6972,7 +7130,7 @@ class MatchDecorator {
6972
7130
  */
6973
7131
  createDeco(view) {
6974
7132
  let build = new rangeset.RangeSetBuilder();
6975
- for (let { from, to } of view.visibleRanges)
7133
+ for (let { from, to } of matchRanges(view, this.maxLength))
6976
7134
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
6977
7135
  return build.finish();
6978
7136
  }
@@ -7289,6 +7447,7 @@ exports.ViewUpdate = ViewUpdate;
7289
7447
  exports.WidgetType = WidgetType;
7290
7448
  exports.__test = __test;
7291
7449
  exports.drawSelection = drawSelection;
7450
+ exports.dropCursor = dropCursor;
7292
7451
  exports.highlightActiveLine = highlightActiveLine;
7293
7452
  exports.highlightSpecialChars = highlightSpecialChars;
7294
7453
  exports.keymap = keymap;