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