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