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