@codemirror/view 0.19.26 → 0.19.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/dist/index.cjs +951 -879
- package/dist/index.d.ts +14 -0
- package/dist/index.js +950 -876
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -655,6 +655,7 @@ class TextView extends ContentView {
|
|
|
655
655
|
split(from) {
|
|
656
656
|
let result = new TextView(this.text.slice(from));
|
|
657
657
|
this.text = this.text.slice(0, from);
|
|
658
|
+
this.markDirty();
|
|
658
659
|
return result;
|
|
659
660
|
}
|
|
660
661
|
localPosFromDOM(node, offset) {
|
|
@@ -842,6 +843,9 @@ class CompositionView extends WidgetView {
|
|
|
842
843
|
coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
|
|
843
844
|
get isEditable() { return true; }
|
|
844
845
|
}
|
|
846
|
+
// Use two characters on Android, to prevent Chrome from closing the
|
|
847
|
+
// virtual keyboard when backspacing after a widget (#602).
|
|
848
|
+
const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
|
|
845
849
|
// These are drawn around uneditable widgets to avoid a number of
|
|
846
850
|
// browser bugs that show up when the cursor is directly next to
|
|
847
851
|
// uneditable inline content.
|
|
@@ -858,12 +862,13 @@ class WidgetBufferView extends ContentView {
|
|
|
858
862
|
split() { return new WidgetBufferView(this.side); }
|
|
859
863
|
sync() {
|
|
860
864
|
if (!this.dom)
|
|
861
|
-
this.setDOM(document.createTextNode(
|
|
862
|
-
else if (this.dirty && this.dom.nodeValue !=
|
|
863
|
-
this.dom.nodeValue =
|
|
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
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
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
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
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 (
|
|
1985
|
-
|
|
1986
|
-
|
|
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
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
2020
|
-
//
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
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
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
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
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
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
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
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
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
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
|
-
|
|
2176
|
-
let
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
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
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
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
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
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
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
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
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
return
|
|
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
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2272
|
-
|
|
2273
|
-
if (!rect)
|
|
2280
|
+
readNode(node) {
|
|
2281
|
+
if (node.cmIgnore)
|
|
2274
2282
|
return;
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
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
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
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
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
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
|
|
2393
|
-
|
|
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
|
|
2399
|
-
constructor() {
|
|
2400
|
-
this.
|
|
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
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
//
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
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
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
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 (
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
-
//
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2397
|
+
// Used by update and the constructor do perform the actual DOM
|
|
2398
|
+
// update
|
|
2399
|
+
updateInner(changes, deco, oldLength) {
|
|
2400
|
+
this.view.viewState.mustMeasureContent = true;
|
|
2401
|
+
this.updateChildren(changes, deco, oldLength);
|
|
2402
|
+
let { observer } = this.view;
|
|
2403
|
+
observer.ignore(() => {
|
|
2404
|
+
// Lock the height during redrawing, since Chrome sometimes
|
|
2405
|
+
// messes with the scroll position during DOM mutation (though
|
|
2406
|
+
// no relayout is triggered and I cannot imagine how it can
|
|
2407
|
+
// recompute the scroll position without a layout)
|
|
2408
|
+
this.dom.style.height = this.view.viewState.contentHeight + "px";
|
|
2409
|
+
this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
|
|
2410
|
+
// Chrome will sometimes, when DOM mutations occur directly
|
|
2411
|
+
// around the selection, get confused and report a different
|
|
2412
|
+
// selection from the one it displays (issue #218). This tries
|
|
2413
|
+
// to detect that situation.
|
|
2414
|
+
let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
|
|
2415
|
+
this.sync(track);
|
|
2416
|
+
this.dirty = 0 /* Not */;
|
|
2417
|
+
if (track && (track.written || observer.selectionRange.focusNode != track.node))
|
|
2418
|
+
this.forceSelection = true;
|
|
2419
|
+
this.dom.style.height = "";
|
|
2420
|
+
});
|
|
2421
|
+
let gaps = [];
|
|
2422
|
+
if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
|
|
2423
|
+
for (let child of this.children)
|
|
2424
|
+
if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
|
|
2425
|
+
gaps.push(child.dom);
|
|
2426
|
+
observer.updateGaps(gaps);
|
|
2427
|
+
}
|
|
2428
|
+
updateChildren(changes, deco, oldLength) {
|
|
2429
|
+
let cursor = this.childCursor(oldLength);
|
|
2430
|
+
for (let i = changes.length - 1;; i--) {
|
|
2431
|
+
let next = i >= 0 ? changes[i] : null;
|
|
2432
|
+
if (!next)
|
|
2433
|
+
break;
|
|
2434
|
+
let { fromA, toA, fromB, toB } = next;
|
|
2435
|
+
let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
|
|
2436
|
+
let { i: toI, off: toOff } = cursor.findPos(toA, 1);
|
|
2437
|
+
let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
|
|
2438
|
+
replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
// Sync the DOM selection to this.state.selection
|
|
2442
|
+
updateSelection(mustRead = false, fromPointer = false) {
|
|
2443
|
+
if (mustRead)
|
|
2444
|
+
this.view.observer.readSelectionRange();
|
|
2445
|
+
if (!(fromPointer || this.mayControlSelection()) ||
|
|
2446
|
+
browser.ios && this.view.inputState.rapidCompositionStart)
|
|
2447
|
+
return;
|
|
2448
|
+
let force = this.forceSelection;
|
|
2449
|
+
this.forceSelection = false;
|
|
2450
|
+
let main = this.view.state.selection.main;
|
|
2451
|
+
// FIXME need to handle the case where the selection falls inside a block range
|
|
2452
|
+
let anchor = this.domAtPos(main.anchor);
|
|
2453
|
+
let head = main.empty ? anchor : this.domAtPos(main.head);
|
|
2454
|
+
// Always reset on Firefox when next to an uneditable node to
|
|
2455
|
+
// avoid invisible cursor bugs (#111)
|
|
2456
|
+
if (browser.gecko && main.empty && betweenUneditable(anchor)) {
|
|
2457
|
+
let dummy = document.createTextNode("");
|
|
2458
|
+
this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
|
|
2459
|
+
anchor = head = new DOMPos(dummy, 0);
|
|
2460
|
+
force = true;
|
|
2461
|
+
}
|
|
2462
|
+
let domSel = this.view.observer.selectionRange;
|
|
2463
|
+
// If the selection is already here, or in an equivalent position, don't touch it
|
|
2464
|
+
if (force || !domSel.focusNode ||
|
|
2465
|
+
!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
|
|
2466
|
+
!isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
|
|
2467
|
+
this.view.observer.ignore(() => {
|
|
2468
|
+
// Chrome Android will hide the virtual keyboard when tapping
|
|
2469
|
+
// inside an uneditable node, and not bring it back when we
|
|
2470
|
+
// move the cursor to its proper position. This tries to
|
|
2471
|
+
// restore the keyboard by cycling focus.
|
|
2472
|
+
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
|
|
2473
|
+
inUneditable(domSel.focusNode, this.dom)) {
|
|
2474
|
+
this.dom.blur();
|
|
2475
|
+
this.dom.focus({ preventScroll: true });
|
|
2476
|
+
}
|
|
2477
|
+
let rawSel = getSelection(this.root);
|
|
2478
|
+
if (main.empty) {
|
|
2479
|
+
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
|
|
2480
|
+
if (browser.gecko) {
|
|
2481
|
+
let nextTo = nextToUneditable(anchor.node, anchor.offset);
|
|
2482
|
+
if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
|
|
2483
|
+
let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
|
|
2484
|
+
if (text)
|
|
2485
|
+
anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
rawSel.collapse(anchor.node, anchor.offset);
|
|
2489
|
+
if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
|
|
2490
|
+
domSel.cursorBidiLevel = main.bidiLevel;
|
|
2491
|
+
}
|
|
2492
|
+
else if (rawSel.extend) {
|
|
2493
|
+
// Selection.extend can be used to create an 'inverted' selection
|
|
2494
|
+
// (one where the focus is before the anchor), but not all
|
|
2495
|
+
// browsers support it yet.
|
|
2496
|
+
rawSel.collapse(anchor.node, anchor.offset);
|
|
2497
|
+
rawSel.extend(head.node, head.offset);
|
|
2498
|
+
}
|
|
2499
|
+
else {
|
|
2500
|
+
// Primitive (IE) way
|
|
2501
|
+
let range = document.createRange();
|
|
2502
|
+
if (main.anchor > main.head)
|
|
2503
|
+
[anchor, head] = [head, anchor];
|
|
2504
|
+
range.setEnd(head.node, head.offset);
|
|
2505
|
+
range.setStart(anchor.node, anchor.offset);
|
|
2506
|
+
rawSel.removeAllRanges();
|
|
2507
|
+
rawSel.addRange(range);
|
|
2508
|
+
}
|
|
2509
|
+
});
|
|
2510
|
+
this.view.observer.setSelectionRange(anchor, head);
|
|
2511
|
+
}
|
|
2512
|
+
this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
|
|
2513
|
+
this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
|
|
2514
|
+
}
|
|
2515
|
+
enforceCursorAssoc() {
|
|
2516
|
+
if (this.compositionDeco.size)
|
|
2517
|
+
return;
|
|
2518
|
+
let cursor = this.view.state.selection.main;
|
|
2519
|
+
let sel = getSelection(this.root);
|
|
2520
|
+
if (!cursor.empty || !cursor.assoc || !sel.modify)
|
|
2521
|
+
return;
|
|
2522
|
+
let line = LineView.find(this, cursor.head);
|
|
2523
|
+
if (!line)
|
|
2524
|
+
return;
|
|
2525
|
+
let lineStart = line.posAtStart;
|
|
2526
|
+
if (cursor.head == lineStart || cursor.head == lineStart + line.length)
|
|
2527
|
+
return;
|
|
2528
|
+
let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
|
|
2529
|
+
if (!before || !after || before.bottom > after.top)
|
|
2530
|
+
return;
|
|
2531
|
+
let dom = this.domAtPos(cursor.head + cursor.assoc);
|
|
2532
|
+
sel.collapse(dom.node, dom.offset);
|
|
2533
|
+
sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
|
|
2549
2534
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
if (type == 128 /* CS */) {
|
|
2561
|
-
if (i < len - 1 && prev == types[i + 1] && (prev & 24 /* Num */))
|
|
2562
|
-
type = types[i] = prev;
|
|
2563
|
-
else
|
|
2564
|
-
types[i] = 256 /* NI */;
|
|
2535
|
+
mayControlSelection() {
|
|
2536
|
+
return this.view.state.facet(editable) ? this.root.activeElement == this.dom
|
|
2537
|
+
: hasSelection(this.dom, this.view.observer.selectionRange);
|
|
2538
|
+
}
|
|
2539
|
+
nearest(dom) {
|
|
2540
|
+
for (let cur = dom; cur;) {
|
|
2541
|
+
let domView = ContentView.get(cur);
|
|
2542
|
+
if (domView && domView.rootView == this)
|
|
2543
|
+
return domView;
|
|
2544
|
+
cur = cur.parentNode;
|
|
2565
2545
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
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
|
-
|
|
2576
|
-
|
|
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
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
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
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
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
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
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 (
|
|
2677
|
-
let
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2686
|
-
|
|
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
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
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
|
-
|
|
2701
|
-
|
|
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
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
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
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
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
|
|
2866
|
-
|
|
2867
|
-
|
|
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.
|
|
2870
|
-
|
|
2871
|
-
|
|
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
|
-
|
|
2875
|
-
|
|
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]
|
|
3194
|
+
let handler = set.handlers[type];
|
|
3114
3195
|
if (handler) {
|
|
3115
3196
|
try {
|
|
3116
|
-
|
|
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(
|
|
3286
|
+
startMouseSelection(mouseSelection) {
|
|
3221
3287
|
if (this.mouseSelection)
|
|
3222
3288
|
this.mouseSelection.destroy();
|
|
3223
|
-
this.mouseSelection =
|
|
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(
|
|
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) ||
|
|
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
|
-
|
|
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.
|
|
3766
|
+
view.observer.delayAndroidKey(pending.key, pending.keyCode);
|
|
3698
3767
|
if (pending.key == "Backspace" || pending.key == "Delete") {
|
|
3699
3768
|
let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
|
|
3700
3769
|
setTimeout(() => {
|
|
@@ -4123,12 +4192,12 @@ class HeightMapBranch extends HeightMap {
|
|
|
4123
4192
|
get break() { return this.flags & 1 /* Break */; }
|
|
4124
4193
|
blockAt(height, doc, top, offset) {
|
|
4125
4194
|
let mid = top + this.left.height;
|
|
4126
|
-
return height < mid
|
|
4195
|
+
return height < mid ? this.left.blockAt(height, doc, top, offset)
|
|
4127
4196
|
: this.right.blockAt(height, doc, mid, offset + this.left.length + this.break);
|
|
4128
4197
|
}
|
|
4129
4198
|
lineAt(value, type, doc, top, offset) {
|
|
4130
4199
|
let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
|
|
4131
|
-
let left = type == QueryType.ByHeight ? value < rightTop
|
|
4200
|
+
let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
|
|
4132
4201
|
let base = left ? this.left.lineAt(value, type, doc, top, offset)
|
|
4133
4202
|
: this.right.lineAt(value, type, doc, rightTop, rightOffset);
|
|
4134
4203
|
if (this.break || (left ? base.to < rightOffset : base.from > rightOffset))
|
|
@@ -4269,7 +4338,9 @@ class NodeBuilder {
|
|
|
4269
4338
|
}
|
|
4270
4339
|
point(from, to, deco) {
|
|
4271
4340
|
if (from < to || deco.heightRelevant) {
|
|
4272
|
-
let height = deco.widget ?
|
|
4341
|
+
let height = deco.widget ? deco.widget.estimatedHeight : 0;
|
|
4342
|
+
if (height < 0)
|
|
4343
|
+
height = this.oracle.lineHeight;
|
|
4273
4344
|
let len = to - from;
|
|
4274
4345
|
if (deco.block) {
|
|
4275
4346
|
this.addBlock(new HeightMapBlock(len, height, deco.type));
|
|
@@ -4397,8 +4468,8 @@ function visiblePixelRange(dom, paddingTop) {
|
|
|
4397
4468
|
break;
|
|
4398
4469
|
}
|
|
4399
4470
|
}
|
|
4400
|
-
return { left: left - rect.left, right: right - rect.left,
|
|
4401
|
-
top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
|
|
4471
|
+
return { left: left - rect.left, right: Math.max(left, right) - rect.left,
|
|
4472
|
+
top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
|
|
4402
4473
|
}
|
|
4403
4474
|
// Line gaps are placeholder widgets used to hide pieces of overlong
|
|
4404
4475
|
// lines within the viewport, as a kludge to keep the editor
|
|
@@ -4625,7 +4696,7 @@ class ViewState {
|
|
|
4625
4696
|
let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
|
|
4626
4697
|
// If scrollTarget is given, make sure the viewport includes that position
|
|
4627
4698
|
if (scrollTarget) {
|
|
4628
|
-
let { head } = scrollTarget.range, viewHeight =
|
|
4699
|
+
let { head } = scrollTarget.range, viewHeight = this.editorHeight;
|
|
4629
4700
|
if (head < viewport.from || head > viewport.to) {
|
|
4630
4701
|
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4631
4702
|
if (scrollTarget.center)
|
|
@@ -4646,6 +4717,8 @@ class ViewState {
|
|
|
4646
4717
|
// Checks if a given viewport covers the visible part of the
|
|
4647
4718
|
// document and not too much beyond that.
|
|
4648
4719
|
viewportIsAppropriate({ from, to }, bias = 0) {
|
|
4720
|
+
if (!this.inView)
|
|
4721
|
+
return true;
|
|
4649
4722
|
let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
|
|
4650
4723
|
let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
|
|
4651
4724
|
let { visibleTop, visibleBottom } = this;
|
|
@@ -4752,8 +4825,11 @@ class ViewState {
|
|
|
4752
4825
|
elementAtHeight(height) {
|
|
4753
4826
|
return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
|
|
4754
4827
|
}
|
|
4828
|
+
get docHeight() {
|
|
4829
|
+
return this.scaler.toDOM(this.heightMap.height);
|
|
4830
|
+
}
|
|
4755
4831
|
get contentHeight() {
|
|
4756
|
-
return this.
|
|
4832
|
+
return this.docHeight + this.paddingTop + this.paddingBottom;
|
|
4757
4833
|
}
|
|
4758
4834
|
}
|
|
4759
4835
|
class Viewport {
|
|
@@ -5007,7 +5083,8 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
|
|
|
5007
5083
|
},
|
|
5008
5084
|
".cm-placeholder": {
|
|
5009
5085
|
color: "#888",
|
|
5010
|
-
display: "inline-block"
|
|
5086
|
+
display: "inline-block",
|
|
5087
|
+
verticalAlign: "top",
|
|
5011
5088
|
},
|
|
5012
5089
|
".cm-button": {
|
|
5013
5090
|
verticalAlign: "middle",
|
|
@@ -5074,6 +5151,7 @@ class DOMObserver {
|
|
|
5074
5151
|
this.delayedFlush = -1;
|
|
5075
5152
|
this.resizeTimeout = -1;
|
|
5076
5153
|
this.queue = [];
|
|
5154
|
+
this.delayedAndroidKey = null;
|
|
5077
5155
|
this.scrollTargets = [];
|
|
5078
5156
|
this.intersection = null;
|
|
5079
5157
|
this.resize = null;
|
|
@@ -5157,7 +5235,7 @@ class DOMObserver {
|
|
|
5157
5235
|
}
|
|
5158
5236
|
}
|
|
5159
5237
|
onSelectionChange(event) {
|
|
5160
|
-
if (!this.readSelectionRange())
|
|
5238
|
+
if (!this.readSelectionRange() || this.delayedAndroidKey)
|
|
5161
5239
|
return;
|
|
5162
5240
|
let { view } = this, sel = this.selectionRange;
|
|
5163
5241
|
if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
|
|
@@ -5253,6 +5331,32 @@ class DOMObserver {
|
|
|
5253
5331
|
this.queue.length = 0;
|
|
5254
5332
|
this.selectionChanged = false;
|
|
5255
5333
|
}
|
|
5334
|
+
// Chrome Android, especially in combination with GBoard, not only
|
|
5335
|
+
// doesn't reliably fire regular key events, but also often
|
|
5336
|
+
// surrounds the effect of enter or backspace with a bunch of
|
|
5337
|
+
// composition events that, when interrupted, cause text duplication
|
|
5338
|
+
// or other kinds of corruption. This hack makes the editor back off
|
|
5339
|
+
// from handling DOM changes for a moment when such a key is
|
|
5340
|
+
// detected (via beforeinput or keydown), and then dispatches the
|
|
5341
|
+
// key event, throwing away the DOM changes if it gets handled.
|
|
5342
|
+
delayAndroidKey(key, keyCode) {
|
|
5343
|
+
if (!this.delayedAndroidKey)
|
|
5344
|
+
requestAnimationFrame(() => {
|
|
5345
|
+
let key = this.delayedAndroidKey;
|
|
5346
|
+
this.delayedAndroidKey = null;
|
|
5347
|
+
let startState = this.view.state;
|
|
5348
|
+
if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
|
|
5349
|
+
this.processRecords();
|
|
5350
|
+
else
|
|
5351
|
+
this.flush();
|
|
5352
|
+
if (this.view.state == startState)
|
|
5353
|
+
this.view.update([]);
|
|
5354
|
+
});
|
|
5355
|
+
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5356
|
+
// Enter always takes precedence.
|
|
5357
|
+
if (!this.delayedAndroidKey || key == "Enter")
|
|
5358
|
+
this.delayedAndroidKey = { key, keyCode };
|
|
5359
|
+
}
|
|
5256
5360
|
flushSoon() {
|
|
5257
5361
|
if (this.delayedFlush < 0)
|
|
5258
5362
|
this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
|
|
@@ -5289,13 +5393,13 @@ class DOMObserver {
|
|
|
5289
5393
|
}
|
|
5290
5394
|
// Apply pending changes, if any
|
|
5291
5395
|
flush(readSelection = true) {
|
|
5292
|
-
if (readSelection)
|
|
5293
|
-
this.readSelectionRange();
|
|
5294
5396
|
// Completely hold off flushing when pending keys are set—the code
|
|
5295
5397
|
// managing those will make sure processRecords is called and the
|
|
5296
5398
|
// view is resynchronized after
|
|
5297
|
-
if (this.delayedFlush >= 0 || this.
|
|
5399
|
+
if (this.delayedFlush >= 0 || this.delayedAndroidKey)
|
|
5298
5400
|
return;
|
|
5401
|
+
if (readSelection)
|
|
5402
|
+
this.readSelectionRange();
|
|
5299
5403
|
let { from, to, typeOver } = this.processRecords();
|
|
5300
5404
|
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5301
5405
|
if (from < 0 && !newSel)
|
|
@@ -5305,7 +5409,7 @@ class DOMObserver {
|
|
|
5305
5409
|
this.onChange(from, to, typeOver);
|
|
5306
5410
|
// The view wasn't updated
|
|
5307
5411
|
if (this.view.state == startState)
|
|
5308
|
-
this.view.
|
|
5412
|
+
this.view.update([]);
|
|
5309
5413
|
}
|
|
5310
5414
|
readMutation(rec) {
|
|
5311
5415
|
let cView = this.view.docView.nearest(rec.target);
|
|
@@ -5436,11 +5540,22 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5436
5540
|
};
|
|
5437
5541
|
if (change) {
|
|
5438
5542
|
let startState = view.state;
|
|
5543
|
+
if (browser.ios && view.inputState.flushIOSKey(view))
|
|
5544
|
+
return;
|
|
5439
5545
|
// Android browsers don't fire reasonable key events for enter,
|
|
5440
5546
|
// backspace, or delete. So this detects changes that look like
|
|
5441
5547
|
// they're caused by those keys, and reinterprets them as key
|
|
5442
|
-
// events.
|
|
5443
|
-
|
|
5548
|
+
// events. (Some of these keys are also handled by beforeinput
|
|
5549
|
+
// events and the pendingAndroidKey mechanism, but that's not
|
|
5550
|
+
// reliable in all situations.)
|
|
5551
|
+
if (browser.android &&
|
|
5552
|
+
((change.from == sel.from && change.to == sel.to &&
|
|
5553
|
+
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
5554
|
+
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
5555
|
+
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5556
|
+
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5557
|
+
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5558
|
+
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
5444
5559
|
return;
|
|
5445
5560
|
let text = change.insert.toString();
|
|
5446
5561
|
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
@@ -5513,76 +5628,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
|
|
|
5513
5628
|
}
|
|
5514
5629
|
return { from, toA, toB };
|
|
5515
5630
|
}
|
|
5516
|
-
class DOMReader {
|
|
5517
|
-
constructor(points, view) {
|
|
5518
|
-
this.points = points;
|
|
5519
|
-
this.view = view;
|
|
5520
|
-
this.text = "";
|
|
5521
|
-
this.lineBreak = view.state.lineBreak;
|
|
5522
|
-
}
|
|
5523
|
-
readRange(start, end) {
|
|
5524
|
-
if (!start)
|
|
5525
|
-
return;
|
|
5526
|
-
let parent = start.parentNode;
|
|
5527
|
-
for (let cur = start;;) {
|
|
5528
|
-
this.findPointBefore(parent, cur);
|
|
5529
|
-
this.readNode(cur);
|
|
5530
|
-
let next = cur.nextSibling;
|
|
5531
|
-
if (next == end)
|
|
5532
|
-
break;
|
|
5533
|
-
let view = ContentView.get(cur), nextView = ContentView.get(next);
|
|
5534
|
-
if (view && nextView ? view.breakAfter :
|
|
5535
|
-
(view ? view.breakAfter : isBlockElement(cur)) ||
|
|
5536
|
-
(isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
|
|
5537
|
-
this.text += this.lineBreak;
|
|
5538
|
-
cur = next;
|
|
5539
|
-
}
|
|
5540
|
-
this.findPointBefore(parent, end);
|
|
5541
|
-
}
|
|
5542
|
-
readNode(node) {
|
|
5543
|
-
if (node.cmIgnore)
|
|
5544
|
-
return;
|
|
5545
|
-
let view = ContentView.get(node);
|
|
5546
|
-
let fromView = view && view.overrideDOMText;
|
|
5547
|
-
let text;
|
|
5548
|
-
if (fromView != null)
|
|
5549
|
-
text = fromView.sliceString(0, undefined, this.lineBreak);
|
|
5550
|
-
else if (node.nodeType == 3)
|
|
5551
|
-
text = node.nodeValue;
|
|
5552
|
-
else if (node.nodeName == "BR")
|
|
5553
|
-
text = node.nextSibling ? this.lineBreak : "";
|
|
5554
|
-
else if (node.nodeType == 1)
|
|
5555
|
-
this.readRange(node.firstChild, null);
|
|
5556
|
-
if (text != null) {
|
|
5557
|
-
this.findPointIn(node, text.length);
|
|
5558
|
-
this.text += text;
|
|
5559
|
-
// Chrome inserts two newlines when pressing shift-enter at the
|
|
5560
|
-
// end of a line. This drops one of those.
|
|
5561
|
-
if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
|
|
5562
|
-
this.text = this.text.slice(0, -1);
|
|
5563
|
-
}
|
|
5564
|
-
}
|
|
5565
|
-
findPointBefore(node, next) {
|
|
5566
|
-
for (let point of this.points)
|
|
5567
|
-
if (point.node == node && node.childNodes[point.offset] == next)
|
|
5568
|
-
point.pos = this.text.length;
|
|
5569
|
-
}
|
|
5570
|
-
findPointIn(node, maxLen) {
|
|
5571
|
-
for (let point of this.points)
|
|
5572
|
-
if (point.node == node)
|
|
5573
|
-
point.pos = this.text.length + Math.min(point.offset, maxLen);
|
|
5574
|
-
}
|
|
5575
|
-
}
|
|
5576
|
-
function isBlockElement(node) {
|
|
5577
|
-
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
|
|
5578
|
-
}
|
|
5579
|
-
class DOMPoint {
|
|
5580
|
-
constructor(node, offset) {
|
|
5581
|
-
this.node = node;
|
|
5582
|
-
this.offset = offset;
|
|
5583
|
-
this.pos = -1;
|
|
5584
|
-
}
|
|
5585
|
-
}
|
|
5586
5631
|
function selectionPoints(view) {
|
|
5587
5632
|
let result = [];
|
|
5588
5633
|
if (view.root.activeElement != view.contentDOM)
|
|
@@ -5665,7 +5710,9 @@ class EditorView {
|
|
|
5665
5710
|
this.dispatch = this.dispatch.bind(this);
|
|
5666
5711
|
this.root = (config.root || getRoot(config.parent) || document);
|
|
5667
5712
|
this.viewState = new ViewState(config.state || EditorState.create());
|
|
5668
|
-
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec)
|
|
5713
|
+
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
|
|
5714
|
+
for (let plugin of this.plugins)
|
|
5715
|
+
plugin.update(this);
|
|
5669
5716
|
this.observer = new DOMObserver(this, (from, to, typeOver) => {
|
|
5670
5717
|
applyDOMChange(this, from, to, typeOver);
|
|
5671
5718
|
}, event => {
|
|
@@ -5802,8 +5849,10 @@ class EditorView {
|
|
|
5802
5849
|
for (let plugin of this.plugins)
|
|
5803
5850
|
plugin.destroy(this);
|
|
5804
5851
|
this.viewState = new ViewState(newState);
|
|
5805
|
-
this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec)
|
|
5852
|
+
this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec));
|
|
5806
5853
|
this.pluginMap.clear();
|
|
5854
|
+
for (let plugin of this.plugins)
|
|
5855
|
+
plugin.update(this);
|
|
5807
5856
|
this.docView = new DocView(this);
|
|
5808
5857
|
this.inputState.ensureHandlers(this);
|
|
5809
5858
|
this.mountStyles();
|
|
@@ -5864,7 +5913,9 @@ class EditorView {
|
|
|
5864
5913
|
if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
|
|
5865
5914
|
break;
|
|
5866
5915
|
if (i > 5) {
|
|
5867
|
-
console.warn(this.measureRequests.length
|
|
5916
|
+
console.warn(this.measureRequests.length
|
|
5917
|
+
? "Measure loop restarted more than 5 times"
|
|
5918
|
+
: "Viewport failed to stabilize");
|
|
5868
5919
|
break;
|
|
5869
5920
|
}
|
|
5870
5921
|
let measuring = [];
|
|
@@ -5910,7 +5961,8 @@ class EditorView {
|
|
|
5910
5961
|
}
|
|
5911
5962
|
if (redrawn)
|
|
5912
5963
|
this.docView.updateSelection(true);
|
|
5913
|
-
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
|
|
5964
|
+
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
|
|
5965
|
+
this.measureRequests.length == 0)
|
|
5914
5966
|
break;
|
|
5915
5967
|
}
|
|
5916
5968
|
}
|
|
@@ -6202,6 +6254,11 @@ class EditorView {
|
|
|
6202
6254
|
Find the DOM parent node and offset (child offset if `node` is
|
|
6203
6255
|
an element, character offset when it is a text node) at the
|
|
6204
6256
|
given document position.
|
|
6257
|
+
|
|
6258
|
+
Note that for positions that aren't currently in
|
|
6259
|
+
`visibleRanges`, the resulting DOM position isn't necessarily
|
|
6260
|
+
meaningful (it may just point before or after a placeholder
|
|
6261
|
+
element).
|
|
6205
6262
|
*/
|
|
6206
6263
|
domAtPos(pos) {
|
|
6207
6264
|
return this.docView.domAtPos(pos);
|
|
@@ -6862,7 +6919,7 @@ function measureRange(view, range) {
|
|
|
6862
6919
|
let between = [];
|
|
6863
6920
|
if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
|
|
6864
6921
|
between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
|
|
6865
|
-
else if (top.bottom < bottom.top &&
|
|
6922
|
+
else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
|
|
6866
6923
|
top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
|
|
6867
6924
|
return pieces(top).concat(between).concat(pieces(bottom));
|
|
6868
6925
|
}
|
|
@@ -6935,6 +6992,22 @@ function iterMatches(doc, re, from, to, f) {
|
|
|
6935
6992
|
f(pos + m.index, pos + m.index + m[0].length, m);
|
|
6936
6993
|
}
|
|
6937
6994
|
}
|
|
6995
|
+
function matchRanges(view, maxLength) {
|
|
6996
|
+
let visible = view.visibleRanges;
|
|
6997
|
+
if (visible.length == 1 && visible[0].from == view.viewport.from &&
|
|
6998
|
+
visible[0].to == view.viewport.to)
|
|
6999
|
+
return visible;
|
|
7000
|
+
let result = [];
|
|
7001
|
+
for (let { from, to } of visible) {
|
|
7002
|
+
from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
|
|
7003
|
+
to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
|
|
7004
|
+
if (result.length && result[result.length - 1].to >= from)
|
|
7005
|
+
result[result.length - 1].to = to;
|
|
7006
|
+
else
|
|
7007
|
+
result.push({ from, to });
|
|
7008
|
+
}
|
|
7009
|
+
return result;
|
|
7010
|
+
}
|
|
6938
7011
|
/**
|
|
6939
7012
|
Helper class used to make it easier to maintain decorations on
|
|
6940
7013
|
visible code that matches a given regular expression. To be used
|
|
@@ -6946,12 +7019,13 @@ class MatchDecorator {
|
|
|
6946
7019
|
Create a decorator.
|
|
6947
7020
|
*/
|
|
6948
7021
|
constructor(config) {
|
|
6949
|
-
let { regexp, decoration, boundary } = config;
|
|
7022
|
+
let { regexp, decoration, boundary, maxLength = 1000 } = config;
|
|
6950
7023
|
if (!regexp.global)
|
|
6951
7024
|
throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
|
|
6952
7025
|
this.regexp = regexp;
|
|
6953
7026
|
this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
|
|
6954
7027
|
this.boundary = boundary;
|
|
7028
|
+
this.maxLength = maxLength;
|
|
6955
7029
|
}
|
|
6956
7030
|
/**
|
|
6957
7031
|
Compute the full set of decorations for matches in the given
|
|
@@ -6960,7 +7034,7 @@ class MatchDecorator {
|
|
|
6960
7034
|
*/
|
|
6961
7035
|
createDeco(view) {
|
|
6962
7036
|
let build = new RangeSetBuilder();
|
|
6963
|
-
for (let { from, to } of view.
|
|
7037
|
+
for (let { from, to } of matchRanges(view, this.maxLength))
|
|
6964
7038
|
iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
|
|
6965
7039
|
return build.finish();
|
|
6966
7040
|
}
|