@codemirror/view 0.19.20 → 0.19.24
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 +38 -0
- package/dist/index.cjs +273 -162
- package/dist/index.d.ts +52 -2
- package/dist/index.js +273 -162
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -186,7 +186,7 @@ function scrollRectIntoView(dom, rect, side, center) {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
class
|
|
189
|
+
class DOMSelectionState {
|
|
190
190
|
constructor() {
|
|
191
191
|
this.anchorNode = null;
|
|
192
192
|
this.anchorOffset = 0;
|
|
@@ -197,11 +197,14 @@ class DOMSelection {
|
|
|
197
197
|
return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
|
|
198
198
|
this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
|
|
199
199
|
}
|
|
200
|
-
|
|
201
|
-
this.anchorNode
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.
|
|
200
|
+
setRange(range) {
|
|
201
|
+
this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
|
|
202
|
+
}
|
|
203
|
+
set(anchorNode, anchorOffset, focusNode, focusOffset) {
|
|
204
|
+
this.anchorNode = anchorNode;
|
|
205
|
+
this.anchorOffset = anchorOffset;
|
|
206
|
+
this.focusNode = focusNode;
|
|
207
|
+
this.focusOffset = focusOffset;
|
|
205
208
|
}
|
|
206
209
|
}
|
|
207
210
|
let preventScrollSupported = null;
|
|
@@ -1909,7 +1912,7 @@ class ViewUpdate {
|
|
|
1909
1912
|
Whether the document changed in this update.
|
|
1910
1913
|
*/
|
|
1911
1914
|
get docChanged() {
|
|
1912
|
-
return this.
|
|
1915
|
+
return !this.changes.empty;
|
|
1913
1916
|
}
|
|
1914
1917
|
/**
|
|
1915
1918
|
Whether the selection was explicitly set in this update.
|
|
@@ -1943,9 +1946,10 @@ class DocView extends ContentView {
|
|
|
1943
1946
|
// we don't mess it up when reading it back it
|
|
1944
1947
|
this.impreciseAnchor = null;
|
|
1945
1948
|
this.impreciseHead = null;
|
|
1949
|
+
this.forceSelection = false;
|
|
1946
1950
|
// Used by the resize observer to ignore resizes that we caused
|
|
1947
1951
|
// ourselves
|
|
1948
|
-
this.lastUpdate =
|
|
1952
|
+
this.lastUpdate = Date.now();
|
|
1949
1953
|
this.setDOM(view.contentDOM);
|
|
1950
1954
|
this.children = [new LineView];
|
|
1951
1955
|
this.children[0].setParent(this);
|
|
@@ -1959,7 +1963,6 @@ class DocView extends ContentView {
|
|
|
1959
1963
|
// position, if we know the editor is going to scroll that position
|
|
1960
1964
|
// into view.
|
|
1961
1965
|
update(update) {
|
|
1962
|
-
this.lastUpdate = Date.now();
|
|
1963
1966
|
let changedRanges = update.changedRanges;
|
|
1964
1967
|
if (this.minWidth > 0 && changedRanges.length) {
|
|
1965
1968
|
if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
|
|
@@ -1979,21 +1982,19 @@ class DocView extends ContentView {
|
|
|
1979
1982
|
// getSelection than the one that it actually shows to the user.
|
|
1980
1983
|
// This forces a selection update when lines are joined to work
|
|
1981
1984
|
// around that. Issue #54
|
|
1982
|
-
|
|
1983
|
-
update.state.doc.lines != update.startState.doc.lines
|
|
1985
|
+
if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
|
|
1986
|
+
update.state.doc.lines != update.startState.doc.lines)
|
|
1987
|
+
this.forceSelection = true;
|
|
1984
1988
|
let prevDeco = this.decorations, deco = this.updateDeco();
|
|
1985
1989
|
let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
|
|
1986
1990
|
changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
|
|
1987
|
-
|
|
1988
|
-
if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
|
|
1989
|
-
!(update.flags & 4 /* Viewport */) &&
|
|
1990
|
-
update.state.selection.main.from >= this.view.viewport.from &&
|
|
1991
|
-
update.state.selection.main.to <= this.view.viewport.to) {
|
|
1992
|
-
this.updateSelection(forceSelection, pointerSel);
|
|
1991
|
+
if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
|
|
1993
1992
|
return false;
|
|
1994
1993
|
}
|
|
1995
1994
|
else {
|
|
1996
|
-
this.updateInner(changedRanges, deco, update.startState.doc.length
|
|
1995
|
+
this.updateInner(changedRanges, deco, update.startState.doc.length);
|
|
1996
|
+
if (update.transactions.length)
|
|
1997
|
+
this.lastUpdate = Date.now();
|
|
1997
1998
|
return true;
|
|
1998
1999
|
}
|
|
1999
2000
|
}
|
|
@@ -2001,13 +2002,16 @@ class DocView extends ContentView {
|
|
|
2001
2002
|
if (this.dirty) {
|
|
2002
2003
|
this.view.observer.ignore(() => this.view.docView.sync());
|
|
2003
2004
|
this.dirty = 0 /* Not */;
|
|
2005
|
+
this.updateSelection(true);
|
|
2004
2006
|
}
|
|
2005
|
-
|
|
2007
|
+
else {
|
|
2006
2008
|
this.updateSelection();
|
|
2009
|
+
}
|
|
2007
2010
|
}
|
|
2008
2011
|
// Used both by update and checkLayout do perform the actual DOM
|
|
2009
2012
|
// update
|
|
2010
|
-
updateInner(changes, deco, oldLength
|
|
2013
|
+
updateInner(changes, deco, oldLength) {
|
|
2014
|
+
this.view.viewState.mustMeasureContent = true;
|
|
2011
2015
|
this.updateChildren(changes, deco, oldLength);
|
|
2012
2016
|
let { observer } = this.view;
|
|
2013
2017
|
observer.ignore(() => {
|
|
@@ -2015,7 +2019,7 @@ class DocView extends ContentView {
|
|
|
2015
2019
|
// messes with the scroll position during DOM mutation (though
|
|
2016
2020
|
// no relayout is triggered and I cannot imagine how it can
|
|
2017
2021
|
// recompute the scroll position without a layout)
|
|
2018
|
-
this.dom.style.height = this.view.viewState.
|
|
2022
|
+
this.dom.style.height = this.view.viewState.contentHeight + "px";
|
|
2019
2023
|
this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
|
|
2020
2024
|
// Chrome will sometimes, when DOM mutations occur directly
|
|
2021
2025
|
// around the selection, get confused and report a different
|
|
@@ -2025,8 +2029,7 @@ class DocView extends ContentView {
|
|
|
2025
2029
|
this.sync(track);
|
|
2026
2030
|
this.dirty = 0 /* Not */;
|
|
2027
2031
|
if (track && (track.written || observer.selectionRange.focusNode != track.node))
|
|
2028
|
-
forceSelection = true;
|
|
2029
|
-
this.updateSelection(forceSelection, pointerSel);
|
|
2032
|
+
this.forceSelection = true;
|
|
2030
2033
|
this.dom.style.height = "";
|
|
2031
2034
|
});
|
|
2032
2035
|
let gaps = [];
|
|
@@ -2112,10 +2115,14 @@ class DocView extends ContentView {
|
|
|
2112
2115
|
this.replaceChildren(fromI, toI, content);
|
|
2113
2116
|
}
|
|
2114
2117
|
// Sync the DOM selection to this.state.selection
|
|
2115
|
-
updateSelection(
|
|
2118
|
+
updateSelection(mustRead = false, fromPointer = false) {
|
|
2119
|
+
if (mustRead)
|
|
2120
|
+
this.view.observer.readSelectionRange();
|
|
2116
2121
|
if (!(fromPointer || this.mayControlSelection()) ||
|
|
2117
2122
|
browser.ios && this.view.inputState.rapidCompositionStart)
|
|
2118
2123
|
return;
|
|
2124
|
+
let force = this.forceSelection;
|
|
2125
|
+
this.forceSelection = false;
|
|
2119
2126
|
let main = this.view.state.selection.main;
|
|
2120
2127
|
// FIXME need to handle the case where the selection falls inside a block range
|
|
2121
2128
|
let anchor = this.domAtPos(main.anchor);
|
|
@@ -2297,7 +2304,7 @@ class DocView extends ContentView {
|
|
|
2297
2304
|
let next = i == vs.viewports.length ? null : vs.viewports[i];
|
|
2298
2305
|
let end = next ? next.from - 1 : this.length;
|
|
2299
2306
|
if (end > pos) {
|
|
2300
|
-
let height = vs.
|
|
2307
|
+
let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
|
|
2301
2308
|
deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
|
|
2302
2309
|
}
|
|
2303
2310
|
if (!next)
|
|
@@ -2904,13 +2911,14 @@ function domPosInText(node, x, y) {
|
|
|
2904
2911
|
}
|
|
2905
2912
|
function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
2906
2913
|
var _a;
|
|
2907
|
-
let content = view.contentDOM.getBoundingClientRect(),
|
|
2914
|
+
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|
2908
2915
|
let halfLine = view.defaultLineHeight / 2;
|
|
2916
|
+
let block, yOffset = y - docTop;
|
|
2909
2917
|
for (let bounced = false;;) {
|
|
2910
|
-
block = view.
|
|
2911
|
-
if (block.top >
|
|
2912
|
-
bias = block.top >
|
|
2913
|
-
|
|
2918
|
+
block = view.elementAtHeight(yOffset);
|
|
2919
|
+
if (block.top > yOffset || block.bottom < yOffset) {
|
|
2920
|
+
bias = block.top > yOffset ? -1 : 1;
|
|
2921
|
+
yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
|
|
2914
2922
|
if (bounced)
|
|
2915
2923
|
return precise ? null : 0;
|
|
2916
2924
|
else
|
|
@@ -2918,8 +2926,9 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
|
2918
2926
|
}
|
|
2919
2927
|
if (block.type == exports.BlockType.Text)
|
|
2920
2928
|
break;
|
|
2921
|
-
|
|
2929
|
+
yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
2922
2930
|
}
|
|
2931
|
+
y = docTop + yOffset;
|
|
2923
2932
|
let lineStart = block.from;
|
|
2924
2933
|
// Clip x to the viewport sides
|
|
2925
2934
|
x = Math.max(content.left + 1, Math.min(content.right - 1, x));
|
|
@@ -3032,17 +3041,17 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3032
3041
|
return state.EditorSelection.cursor(startPos);
|
|
3033
3042
|
let goal = start.goalColumn, startY;
|
|
3034
3043
|
let rect = view.contentDOM.getBoundingClientRect();
|
|
3035
|
-
let startCoords = view.coordsAtPos(startPos);
|
|
3044
|
+
let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
|
|
3036
3045
|
if (startCoords) {
|
|
3037
3046
|
if (goal == null)
|
|
3038
3047
|
goal = startCoords.left - rect.left;
|
|
3039
3048
|
startY = dir < 0 ? startCoords.top : startCoords.bottom;
|
|
3040
3049
|
}
|
|
3041
3050
|
else {
|
|
3042
|
-
let line = view.viewState.
|
|
3051
|
+
let line = view.viewState.lineBlockAt(startPos - docTop);
|
|
3043
3052
|
if (goal == null)
|
|
3044
3053
|
goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
|
|
3045
|
-
startY = dir < 0 ? line.top : line.bottom;
|
|
3054
|
+
startY = (dir < 0 ? line.top : line.bottom) + docTop;
|
|
3046
3055
|
}
|
|
3047
3056
|
let resolvedGoal = rect.left + goal;
|
|
3048
3057
|
let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
|
|
@@ -3293,7 +3302,7 @@ class MouseSelection {
|
|
|
3293
3302
|
this.extend = startEvent.shiftKey;
|
|
3294
3303
|
this.multiple = view.state.facet(state.EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
|
|
3295
3304
|
this.dragMove = dragMovesSelection(view, startEvent);
|
|
3296
|
-
this.dragging = isInPrimarySelection(view, startEvent) ? null : false;
|
|
3305
|
+
this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
|
|
3297
3306
|
// When clicking outside of the selection, immediately apply the
|
|
3298
3307
|
// effect of starting the selection
|
|
3299
3308
|
if (this.dragging === false) {
|
|
@@ -3514,7 +3523,7 @@ function basicMouseSelection(view, event) {
|
|
|
3514
3523
|
let last = start, lastEvent = event;
|
|
3515
3524
|
return {
|
|
3516
3525
|
update(update) {
|
|
3517
|
-
if (update.
|
|
3526
|
+
if (update.docChanged) {
|
|
3518
3527
|
if (start)
|
|
3519
3528
|
start.pos = update.changes.mapPos(start.pos);
|
|
3520
3529
|
startSel = startSel.map(update.changes);
|
|
@@ -3778,7 +3787,10 @@ class HeightOracle {
|
|
|
3778
3787
|
return lines * this.lineHeight;
|
|
3779
3788
|
}
|
|
3780
3789
|
setDoc(doc) { this.doc = doc; return this; }
|
|
3781
|
-
|
|
3790
|
+
mustRefreshForStyle(whiteSpace, direction) {
|
|
3791
|
+
return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
|
|
3792
|
+
}
|
|
3793
|
+
mustRefreshForHeights(lineHeights) {
|
|
3782
3794
|
let newHeight = false;
|
|
3783
3795
|
for (let i = 0; i < lineHeights.length; i++) {
|
|
3784
3796
|
let h = lineHeights[i];
|
|
@@ -3790,7 +3802,7 @@ class HeightOracle {
|
|
|
3790
3802
|
this.heightSamples[Math.floor(h * 10)] = true;
|
|
3791
3803
|
}
|
|
3792
3804
|
}
|
|
3793
|
-
return newHeight
|
|
3805
|
+
return newHeight;
|
|
3794
3806
|
}
|
|
3795
3807
|
refresh(whiteSpace, direction, lineHeight, charWidth, lineLength, knownHeights) {
|
|
3796
3808
|
let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
|
|
@@ -3844,7 +3856,8 @@ class BlockInfo {
|
|
|
3844
3856
|
*/
|
|
3845
3857
|
length,
|
|
3846
3858
|
/**
|
|
3847
|
-
The top position of the element
|
|
3859
|
+
The top position of the element (relative to the top of the
|
|
3860
|
+
document).
|
|
3848
3861
|
*/
|
|
3849
3862
|
top,
|
|
3850
3863
|
/**
|
|
@@ -3878,6 +3891,12 @@ class BlockInfo {
|
|
|
3878
3891
|
.concat(Array.isArray(other.type) ? other.type : [other]);
|
|
3879
3892
|
return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, detail);
|
|
3880
3893
|
}
|
|
3894
|
+
/**
|
|
3895
|
+
FIXME remove on next breaking release @internal
|
|
3896
|
+
*/
|
|
3897
|
+
moveY(offset) {
|
|
3898
|
+
return !offset ? this : new BlockInfo(this.from, this.length, this.top + offset, this.height, Array.isArray(this.type) ? this.type.map(b => b.moveY(offset)) : this.type);
|
|
3899
|
+
}
|
|
3881
3900
|
}
|
|
3882
3901
|
var QueryType;
|
|
3883
3902
|
(function (QueryType) {
|
|
@@ -3885,7 +3904,7 @@ var QueryType;
|
|
|
3885
3904
|
QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
|
|
3886
3905
|
QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
|
|
3887
3906
|
})(QueryType || (QueryType = {}));
|
|
3888
|
-
const Epsilon = 1e-
|
|
3907
|
+
const Epsilon = 1e-3;
|
|
3889
3908
|
class HeightMap {
|
|
3890
3909
|
constructor(length, // The number of characters covered
|
|
3891
3910
|
height, // Height of this part of the document
|
|
@@ -4112,22 +4131,30 @@ class HeightMapGap extends HeightMap {
|
|
|
4112
4131
|
// can't be widgets or collapsed ranges in those lines, because
|
|
4113
4132
|
// they would already have been added to the heightmap (gaps
|
|
4114
4133
|
// only contain plain text).
|
|
4115
|
-
let nodes = [], pos = Math.max(offset, measured.from);
|
|
4134
|
+
let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
|
|
4135
|
+
let wasChanged = oracle.heightChanged;
|
|
4116
4136
|
if (measured.from > offset)
|
|
4117
4137
|
nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
|
|
4118
4138
|
while (pos <= end && measured.more) {
|
|
4119
4139
|
let len = oracle.doc.lineAt(pos).length;
|
|
4120
4140
|
if (nodes.length)
|
|
4121
4141
|
nodes.push(null);
|
|
4122
|
-
let
|
|
4142
|
+
let height = measured.heights[measured.index++];
|
|
4143
|
+
if (singleHeight == -1)
|
|
4144
|
+
singleHeight = height;
|
|
4145
|
+
else if (Math.abs(height - singleHeight) >= Epsilon)
|
|
4146
|
+
singleHeight = -2;
|
|
4147
|
+
let line = new HeightMapText(len, height);
|
|
4123
4148
|
line.outdated = false;
|
|
4124
4149
|
nodes.push(line);
|
|
4125
4150
|
pos += len + 1;
|
|
4126
4151
|
}
|
|
4127
4152
|
if (pos <= end)
|
|
4128
4153
|
nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
|
|
4129
|
-
|
|
4130
|
-
|
|
4154
|
+
let result = HeightMap.of(nodes);
|
|
4155
|
+
oracle.heightChanged = wasChanged || singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
|
|
4156
|
+
Math.abs(singleHeight - this.lines(oracle.doc, offset).lineHeight) >= Epsilon;
|
|
4157
|
+
return result;
|
|
4131
4158
|
}
|
|
4132
4159
|
else if (force || this.outdated) {
|
|
4133
4160
|
this.setHeight(oracle, oracle.heightForGap(offset, offset + this.length));
|
|
@@ -4485,7 +4512,8 @@ class ViewState {
|
|
|
4485
4512
|
this.inView = true;
|
|
4486
4513
|
this.paddingTop = 0;
|
|
4487
4514
|
this.paddingBottom = 0;
|
|
4488
|
-
this.
|
|
4515
|
+
this.contentDOMWidth = 0;
|
|
4516
|
+
this.contentDOMHeight = 0;
|
|
4489
4517
|
this.editorHeight = 0;
|
|
4490
4518
|
this.heightOracle = new HeightOracle;
|
|
4491
4519
|
// See VP.MaxDOMHeight
|
|
@@ -4493,6 +4521,9 @@ class ViewState {
|
|
|
4493
4521
|
this.scrollTarget = null;
|
|
4494
4522
|
// Briefly set to true when printing, to disable viewport limiting
|
|
4495
4523
|
this.printing = false;
|
|
4524
|
+
// Flag set when editor content was redrawn, so that the next
|
|
4525
|
+
// measure stage knows it must read DOM layout
|
|
4526
|
+
this.mustMeasureContent = true;
|
|
4496
4527
|
this.visibleRanges = [];
|
|
4497
4528
|
// Cursor 'assoc' is only significant when the cursor is on a line
|
|
4498
4529
|
// wrap point, where it must stick to the character that it is
|
|
@@ -4505,6 +4536,7 @@ class ViewState {
|
|
|
4505
4536
|
this.mustEnforceCursorAssoc = false;
|
|
4506
4537
|
this.heightMap = HeightMap.empty().applyChanges(state.facet(decorations), text.Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
|
|
4507
4538
|
this.viewport = this.getViewport(0, null);
|
|
4539
|
+
this.updateViewportLines();
|
|
4508
4540
|
this.updateForViewport();
|
|
4509
4541
|
this.lineGaps = this.ensureLineGaps([]);
|
|
4510
4542
|
this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(false)));
|
|
@@ -4515,7 +4547,7 @@ class ViewState {
|
|
|
4515
4547
|
for (let i = 0; i <= 1; i++) {
|
|
4516
4548
|
let pos = i ? main.head : main.anchor;
|
|
4517
4549
|
if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
|
|
4518
|
-
let { from, to } = this.
|
|
4550
|
+
let { from, to } = this.lineBlockAt(pos);
|
|
4519
4551
|
viewports.push(new Viewport(from, to));
|
|
4520
4552
|
}
|
|
4521
4553
|
}
|
|
@@ -4523,6 +4555,12 @@ class ViewState {
|
|
|
4523
4555
|
this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
|
|
4524
4556
|
new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
|
|
4525
4557
|
}
|
|
4558
|
+
updateViewportLines() {
|
|
4559
|
+
this.viewportLines = [];
|
|
4560
|
+
this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, block => {
|
|
4561
|
+
this.viewportLines.push(this.scaler.scale == 1 ? block : scaleBlock(block, this.scaler));
|
|
4562
|
+
});
|
|
4563
|
+
}
|
|
4526
4564
|
update(update, scrollTarget = null) {
|
|
4527
4565
|
let prev = this.state;
|
|
4528
4566
|
this.state = update.state;
|
|
@@ -4537,7 +4575,11 @@ class ViewState {
|
|
|
4537
4575
|
if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
|
|
4538
4576
|
!this.viewportIsAppropriate(viewport))
|
|
4539
4577
|
viewport = this.getViewport(0, scrollTarget);
|
|
4578
|
+
let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
|
|
4579
|
+
viewport.from != this.viewport.from || viewport.to != this.viewport.to;
|
|
4540
4580
|
this.viewport = viewport;
|
|
4581
|
+
if (updateLines)
|
|
4582
|
+
this.updateViewportLines();
|
|
4541
4583
|
this.updateForViewport();
|
|
4542
4584
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4543
4585
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
@@ -4548,13 +4590,17 @@ class ViewState {
|
|
|
4548
4590
|
update.state.selection.main.empty && update.state.selection.main.assoc)
|
|
4549
4591
|
this.mustEnforceCursorAssoc = true;
|
|
4550
4592
|
}
|
|
4551
|
-
measure(
|
|
4552
|
-
let dom =
|
|
4553
|
-
let
|
|
4554
|
-
|
|
4593
|
+
measure(view) {
|
|
4594
|
+
let dom = view.contentDOM, style = window.getComputedStyle(dom);
|
|
4595
|
+
let oracle = this.heightOracle;
|
|
4596
|
+
let whiteSpace = style.whiteSpace, direction = style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR;
|
|
4597
|
+
let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
|
|
4598
|
+
let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
|
|
4599
|
+
let result = 0, bias = 0;
|
|
4600
|
+
if (measureContent) {
|
|
4601
|
+
this.mustMeasureContent = false;
|
|
4602
|
+
this.contentDOMHeight = dom.clientHeight;
|
|
4555
4603
|
// Vertical padding
|
|
4556
|
-
let style = window.getComputedStyle(dom);
|
|
4557
|
-
whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
|
|
4558
4604
|
let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
|
|
4559
4605
|
if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
|
|
4560
4606
|
result |= 8 /* Geometry */;
|
|
@@ -4569,39 +4615,42 @@ class ViewState {
|
|
|
4569
4615
|
this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
|
|
4570
4616
|
if (!this.inView)
|
|
4571
4617
|
return 0;
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
let { lineHeight, charWidth } = docView.measureTextSize();
|
|
4618
|
+
if (measureContent) {
|
|
4619
|
+
let lineHeights = view.docView.measureVisibleLineHeights();
|
|
4620
|
+
if (oracle.mustRefreshForHeights(lineHeights))
|
|
4621
|
+
refresh = true;
|
|
4622
|
+
let contentWidth = dom.clientWidth;
|
|
4623
|
+
if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
|
|
4624
|
+
let { lineHeight, charWidth } = view.docView.measureTextSize();
|
|
4579
4625
|
refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
4580
4626
|
if (refresh) {
|
|
4581
|
-
docView.minWidth = 0;
|
|
4627
|
+
view.docView.minWidth = 0;
|
|
4582
4628
|
result |= 8 /* Geometry */;
|
|
4583
4629
|
}
|
|
4584
4630
|
}
|
|
4585
|
-
if (this.
|
|
4586
|
-
this.
|
|
4631
|
+
if (this.contentDOMWidth != contentWidth) {
|
|
4632
|
+
this.contentDOMWidth = contentWidth;
|
|
4587
4633
|
result |= 8 /* Geometry */;
|
|
4588
4634
|
}
|
|
4589
|
-
if (this.editorHeight !=
|
|
4590
|
-
this.editorHeight =
|
|
4635
|
+
if (this.editorHeight != view.scrollDOM.clientHeight) {
|
|
4636
|
+
this.editorHeight = view.scrollDOM.clientHeight;
|
|
4591
4637
|
result |= 8 /* Geometry */;
|
|
4592
4638
|
}
|
|
4593
4639
|
if (dTop > 0 && dBottom > 0)
|
|
4594
4640
|
bias = Math.max(dTop, dBottom);
|
|
4595
4641
|
else if (dTop < 0 && dBottom < 0)
|
|
4596
4642
|
bias = Math.min(dTop, dBottom);
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to)
|
|
4643
|
+
oracle.heightChanged = false;
|
|
4644
|
+
this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
|
|
4645
|
+
if (oracle.heightChanged)
|
|
4646
|
+
result |= 2 /* Height */;
|
|
4647
|
+
}
|
|
4648
|
+
let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
|
|
4649
|
+
this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
|
|
4650
|
+
if (viewportChange)
|
|
4604
4651
|
this.viewport = this.getViewport(bias, this.scrollTarget);
|
|
4652
|
+
if ((result & 2 /* Height */) || viewportChange)
|
|
4653
|
+
this.updateViewportLines();
|
|
4605
4654
|
this.updateForViewport();
|
|
4606
4655
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4607
4656
|
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
@@ -4612,12 +4661,12 @@ class ViewState {
|
|
|
4612
4661
|
// to a line end is going to trigger a layout anyway, so it
|
|
4613
4662
|
// can't be a pure write. It should be rare that it does any
|
|
4614
4663
|
// writing.
|
|
4615
|
-
docView.enforceCursorAssoc();
|
|
4664
|
+
view.docView.enforceCursorAssoc();
|
|
4616
4665
|
}
|
|
4617
4666
|
return result;
|
|
4618
4667
|
}
|
|
4619
|
-
get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top
|
|
4620
|
-
get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom
|
|
4668
|
+
get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
|
|
4669
|
+
get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
|
|
4621
4670
|
getViewport(bias, scrollTarget) {
|
|
4622
4671
|
// This will divide VP.Margin between the top and the
|
|
4623
4672
|
// bottom, depending on the bias (the change in viewport position
|
|
@@ -4677,12 +4726,12 @@ class ViewState {
|
|
|
4677
4726
|
// This won't work at all in predominantly right-to-left text.
|
|
4678
4727
|
if (this.heightOracle.direction != exports.Direction.LTR)
|
|
4679
4728
|
return gaps;
|
|
4680
|
-
|
|
4729
|
+
for (let line of this.viewportLines) {
|
|
4681
4730
|
if (line.length < 4000 /* DoubleMargin */)
|
|
4682
|
-
|
|
4731
|
+
continue;
|
|
4683
4732
|
let structure = lineStructure(line.from, line.to, this.state);
|
|
4684
4733
|
if (structure.total < 4000 /* DoubleMargin */)
|
|
4685
|
-
|
|
4734
|
+
continue;
|
|
4686
4735
|
let viewFrom, viewTo;
|
|
4687
4736
|
if (this.heightOracle.lineWrapping) {
|
|
4688
4737
|
let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
|
|
@@ -4712,7 +4761,7 @@ class ViewState {
|
|
|
4712
4761
|
Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
|
|
4713
4762
|
new LineGap(from, to, this.gapSize(line, from, to, structure)));
|
|
4714
4763
|
}
|
|
4715
|
-
}
|
|
4764
|
+
}
|
|
4716
4765
|
return gaps;
|
|
4717
4766
|
}
|
|
4718
4767
|
gapSize(line, from, to, structure) {
|
|
@@ -4744,27 +4793,18 @@ class ViewState {
|
|
|
4744
4793
|
this.visibleRanges = ranges;
|
|
4745
4794
|
return changed ? 4 /* Viewport */ : 0;
|
|
4746
4795
|
}
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4796
|
+
lineBlockAt(pos) {
|
|
4797
|
+
return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
|
|
4798
|
+
scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, 0, 0), this.scaler);
|
|
4750
4799
|
}
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height, editorTop), QueryType.ByHeight, this.state.doc, editorTop, 0), this.scaler, editorTop);
|
|
4800
|
+
lineBlockAtHeight(height) {
|
|
4801
|
+
return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.state.doc, 0, 0), this.scaler);
|
|
4754
4802
|
}
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height, editorTop), this.state.doc, editorTop, 0), this.scaler, editorTop);
|
|
4758
|
-
}
|
|
4759
|
-
forEachLine(from, to, f, editorTop) {
|
|
4760
|
-
editorTop += this.paddingTop;
|
|
4761
|
-
return this.heightMap.forEachLine(from, to, this.state.doc, editorTop, 0, this.scaler.scale == 1 ? f : b => f(scaleBlock(b, this.scaler, editorTop)));
|
|
4803
|
+
elementAtHeight(height) {
|
|
4804
|
+
return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
|
|
4762
4805
|
}
|
|
4763
4806
|
get contentHeight() {
|
|
4764
|
-
return this.
|
|
4765
|
-
}
|
|
4766
|
-
get domHeight() {
|
|
4767
|
-
return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
|
|
4807
|
+
return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
|
|
4768
4808
|
}
|
|
4769
4809
|
}
|
|
4770
4810
|
class Viewport {
|
|
@@ -4861,36 +4901,34 @@ class BigScaler {
|
|
|
4861
4901
|
base = obj.bottom;
|
|
4862
4902
|
}
|
|
4863
4903
|
}
|
|
4864
|
-
toDOM(n
|
|
4865
|
-
n -= top;
|
|
4904
|
+
toDOM(n) {
|
|
4866
4905
|
for (let i = 0, base = 0, domBase = 0;; i++) {
|
|
4867
4906
|
let vp = i < this.viewports.length ? this.viewports[i] : null;
|
|
4868
4907
|
if (!vp || n < vp.top)
|
|
4869
|
-
return domBase + (n - base) * this.scale
|
|
4908
|
+
return domBase + (n - base) * this.scale;
|
|
4870
4909
|
if (n <= vp.bottom)
|
|
4871
|
-
return vp.domTop + (n - vp.top)
|
|
4910
|
+
return vp.domTop + (n - vp.top);
|
|
4872
4911
|
base = vp.bottom;
|
|
4873
4912
|
domBase = vp.domBottom;
|
|
4874
4913
|
}
|
|
4875
4914
|
}
|
|
4876
|
-
fromDOM(n
|
|
4877
|
-
n -= top;
|
|
4915
|
+
fromDOM(n) {
|
|
4878
4916
|
for (let i = 0, base = 0, domBase = 0;; i++) {
|
|
4879
4917
|
let vp = i < this.viewports.length ? this.viewports[i] : null;
|
|
4880
4918
|
if (!vp || n < vp.domTop)
|
|
4881
|
-
return base + (n - domBase) / this.scale
|
|
4919
|
+
return base + (n - domBase) / this.scale;
|
|
4882
4920
|
if (n <= vp.domBottom)
|
|
4883
|
-
return vp.top + (n - vp.domTop)
|
|
4921
|
+
return vp.top + (n - vp.domTop);
|
|
4884
4922
|
base = vp.bottom;
|
|
4885
4923
|
domBase = vp.domBottom;
|
|
4886
4924
|
}
|
|
4887
4925
|
}
|
|
4888
4926
|
}
|
|
4889
|
-
function scaleBlock(block, scaler
|
|
4927
|
+
function scaleBlock(block, scaler) {
|
|
4890
4928
|
if (scaler.scale == 1)
|
|
4891
4929
|
return block;
|
|
4892
|
-
let bTop = scaler.toDOM(block.top
|
|
4893
|
-
return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler
|
|
4930
|
+
let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
|
|
4931
|
+
return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler)) : block.type);
|
|
4894
4932
|
}
|
|
4895
4933
|
|
|
4896
4934
|
const theme = state.Facet.define({ combine: strs => strs.join(" ") });
|
|
@@ -5075,25 +5113,30 @@ class DOMObserver {
|
|
|
5075
5113
|
this.onChange = onChange;
|
|
5076
5114
|
this.onScrollChanged = onScrollChanged;
|
|
5077
5115
|
this.active = false;
|
|
5078
|
-
|
|
5116
|
+
// The known selection. Kept in our own object, as opposed to just
|
|
5117
|
+
// directly accessing the selection because:
|
|
5118
|
+
// - Safari doesn't report the right selection in shadow DOM
|
|
5119
|
+
// - Reading from the selection forces a DOM layout
|
|
5120
|
+
// - This way, we can ignore selectionchange events if we have
|
|
5121
|
+
// already seen the 'new' selection
|
|
5122
|
+
this.selectionRange = new DOMSelectionState;
|
|
5123
|
+
// Set when a selection change is detected, cleared on flush
|
|
5124
|
+
this.selectionChanged = false;
|
|
5079
5125
|
this.delayedFlush = -1;
|
|
5126
|
+
this.resizeTimeout = -1;
|
|
5080
5127
|
this.queue = [];
|
|
5081
|
-
this.lastFlush = 0;
|
|
5082
5128
|
this.scrollTargets = [];
|
|
5083
5129
|
this.intersection = null;
|
|
5084
5130
|
this.resize = null;
|
|
5085
5131
|
this.intersecting = false;
|
|
5086
5132
|
this.gapIntersection = null;
|
|
5087
5133
|
this.gaps = [];
|
|
5088
|
-
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
5089
|
-
this._selectionRange = null;
|
|
5090
5134
|
// Timeout for scheduling check of the parents that need scroll handlers
|
|
5091
5135
|
this.parentCheck = -1;
|
|
5092
5136
|
this.dom = view.contentDOM;
|
|
5093
5137
|
this.observer = new MutationObserver(mutations => {
|
|
5094
5138
|
for (let mut of mutations)
|
|
5095
5139
|
this.queue.push(mut);
|
|
5096
|
-
this._selectionRange = null;
|
|
5097
5140
|
// IE11 will sometimes (on typing over a selection or
|
|
5098
5141
|
// backspacing out a single character text node) call the
|
|
5099
5142
|
// observer callback before actually updating the DOM.
|
|
@@ -5120,8 +5163,11 @@ class DOMObserver {
|
|
|
5120
5163
|
this.onSelectionChange = this.onSelectionChange.bind(this);
|
|
5121
5164
|
if (typeof ResizeObserver == "function") {
|
|
5122
5165
|
this.resize = new ResizeObserver(() => {
|
|
5123
|
-
if (this.view.docView.lastUpdate < Date.now() -
|
|
5124
|
-
this.
|
|
5166
|
+
if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
|
|
5167
|
+
this.resizeTimeout = setTimeout(() => {
|
|
5168
|
+
this.resizeTimeout = -1;
|
|
5169
|
+
this.view.requestMeasure();
|
|
5170
|
+
}, 50);
|
|
5125
5171
|
});
|
|
5126
5172
|
this.resize.observe(view.scrollDOM);
|
|
5127
5173
|
}
|
|
@@ -5145,10 +5191,12 @@ class DOMObserver {
|
|
|
5145
5191
|
}, {});
|
|
5146
5192
|
}
|
|
5147
5193
|
this.listenForScroll();
|
|
5194
|
+
this.readSelectionRange();
|
|
5195
|
+
this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
|
|
5148
5196
|
}
|
|
5149
5197
|
onScroll(e) {
|
|
5150
5198
|
if (this.intersecting)
|
|
5151
|
-
this.flush();
|
|
5199
|
+
this.flush(false);
|
|
5152
5200
|
this.onScrollChanged(e);
|
|
5153
5201
|
}
|
|
5154
5202
|
updateGaps(gaps) {
|
|
@@ -5160,8 +5208,8 @@ class DOMObserver {
|
|
|
5160
5208
|
}
|
|
5161
5209
|
}
|
|
5162
5210
|
onSelectionChange(event) {
|
|
5163
|
-
if (this.
|
|
5164
|
-
|
|
5211
|
+
if (!this.readSelectionRange())
|
|
5212
|
+
return;
|
|
5165
5213
|
let { view } = this, sel = this.selectionRange;
|
|
5166
5214
|
if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
|
|
5167
5215
|
return;
|
|
@@ -5176,24 +5224,22 @@ class DOMObserver {
|
|
|
5176
5224
|
sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
|
|
5177
5225
|
this.flushSoon();
|
|
5178
5226
|
else
|
|
5179
|
-
this.flush();
|
|
5180
|
-
}
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
return this.
|
|
5227
|
+
this.flush(false);
|
|
5228
|
+
}
|
|
5229
|
+
readSelectionRange() {
|
|
5230
|
+
let { root } = this.view, domSel = getSelection(root);
|
|
5231
|
+
// The Selection object is broken in shadow roots in Safari. See
|
|
5232
|
+
// https://github.com/codemirror/codemirror.next/issues/414
|
|
5233
|
+
let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
|
|
5234
|
+
safariSelectionRangeHack(this.view) || domSel;
|
|
5235
|
+
if (this.selectionRange.eq(range))
|
|
5236
|
+
return false;
|
|
5237
|
+
this.selectionRange.setRange(range);
|
|
5238
|
+
return this.selectionChanged = true;
|
|
5191
5239
|
}
|
|
5192
5240
|
setSelectionRange(anchor, head) {
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
this._selectionRange = { anchorNode: anchor.node, anchorOffset: anchor.offset,
|
|
5196
|
-
focusNode: head.node, focusOffset: head.offset };
|
|
5241
|
+
this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
|
|
5242
|
+
this.selectionChanged = false;
|
|
5197
5243
|
}
|
|
5198
5244
|
listenForScroll() {
|
|
5199
5245
|
this.parentCheck = -1;
|
|
@@ -5240,7 +5286,6 @@ class DOMObserver {
|
|
|
5240
5286
|
if (this.active)
|
|
5241
5287
|
return;
|
|
5242
5288
|
this.observer.observe(this.dom, observeOptions);
|
|
5243
|
-
this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
|
|
5244
5289
|
if (useCharData)
|
|
5245
5290
|
this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
|
|
5246
5291
|
this.active = true;
|
|
@@ -5250,18 +5295,14 @@ class DOMObserver {
|
|
|
5250
5295
|
return;
|
|
5251
5296
|
this.active = false;
|
|
5252
5297
|
this.observer.disconnect();
|
|
5253
|
-
this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
|
|
5254
5298
|
if (useCharData)
|
|
5255
5299
|
this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
|
|
5256
5300
|
}
|
|
5257
|
-
clearSelection() {
|
|
5258
|
-
this.ignoreSelection.set(this.selectionRange);
|
|
5259
|
-
}
|
|
5260
5301
|
// Throw away any pending changes
|
|
5261
5302
|
clear() {
|
|
5262
5303
|
this.observer.takeRecords();
|
|
5263
5304
|
this.queue.length = 0;
|
|
5264
|
-
this.
|
|
5305
|
+
this.selectionChanged = false;
|
|
5265
5306
|
}
|
|
5266
5307
|
flushSoon() {
|
|
5267
5308
|
if (this.delayedFlush < 0)
|
|
@@ -5298,24 +5339,24 @@ class DOMObserver {
|
|
|
5298
5339
|
return { from, to, typeOver };
|
|
5299
5340
|
}
|
|
5300
5341
|
// Apply pending changes, if any
|
|
5301
|
-
flush() {
|
|
5342
|
+
flush(readSelection = true) {
|
|
5343
|
+
if (readSelection)
|
|
5344
|
+
this.readSelectionRange();
|
|
5302
5345
|
// Completely hold off flushing when pending keys are set—the code
|
|
5303
5346
|
// managing those will make sure processRecords is called and the
|
|
5304
5347
|
// view is resynchronized after
|
|
5305
5348
|
if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
|
|
5306
5349
|
return;
|
|
5307
|
-
this.lastFlush = Date.now();
|
|
5308
5350
|
let { from, to, typeOver } = this.processRecords();
|
|
5309
|
-
let
|
|
5310
|
-
let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
|
|
5351
|
+
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5311
5352
|
if (from < 0 && !newSel)
|
|
5312
5353
|
return;
|
|
5354
|
+
this.selectionChanged = false;
|
|
5313
5355
|
let startState = this.view.state;
|
|
5314
5356
|
this.onChange(from, to, typeOver);
|
|
5315
5357
|
// The view wasn't updated
|
|
5316
5358
|
if (this.view.state == startState)
|
|
5317
5359
|
this.view.docView.reset(newSel);
|
|
5318
|
-
this.clearSelection();
|
|
5319
5360
|
}
|
|
5320
5361
|
readMutation(rec) {
|
|
5321
5362
|
let cView = this.view.docView.nearest(rec.target);
|
|
@@ -5347,6 +5388,7 @@ class DOMObserver {
|
|
|
5347
5388
|
dom.removeEventListener("scroll", this.onScroll);
|
|
5348
5389
|
window.removeEventListener("scroll", this.onScroll);
|
|
5349
5390
|
clearTimeout(this.parentCheck);
|
|
5391
|
+
clearTimeout(this.resizeTimeout);
|
|
5350
5392
|
}
|
|
5351
5393
|
}
|
|
5352
5394
|
function findChild(cView, dom, dir) {
|
|
@@ -5359,6 +5401,7 @@ function findChild(cView, dom, dir) {
|
|
|
5359
5401
|
}
|
|
5360
5402
|
return null;
|
|
5361
5403
|
}
|
|
5404
|
+
// Used to work around a Safari Selection/shadow DOM bug (#414)
|
|
5362
5405
|
function safariSelectionRangeHack(view) {
|
|
5363
5406
|
let found = null;
|
|
5364
5407
|
// Because Safari (at least in 2018-2021) doesn't provide regular
|
|
@@ -5778,6 +5821,7 @@ class EditorView {
|
|
|
5778
5821
|
this.mountStyles();
|
|
5779
5822
|
this.updateAttrs();
|
|
5780
5823
|
this.showAnnouncements(transactions);
|
|
5824
|
+
this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
|
|
5781
5825
|
}
|
|
5782
5826
|
finally {
|
|
5783
5827
|
this.updateState = 0 /* Idle */;
|
|
@@ -5863,11 +5907,11 @@ class EditorView {
|
|
|
5863
5907
|
for (let i = 0;; i++) {
|
|
5864
5908
|
this.updateState = 1 /* Measuring */;
|
|
5865
5909
|
let oldViewport = this.viewport;
|
|
5866
|
-
let changed = this.viewState.measure(this
|
|
5910
|
+
let changed = this.viewState.measure(this);
|
|
5867
5911
|
if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
|
|
5868
5912
|
break;
|
|
5869
5913
|
if (i > 5) {
|
|
5870
|
-
console.warn("Viewport failed to stabilize");
|
|
5914
|
+
console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
|
|
5871
5915
|
break;
|
|
5872
5916
|
}
|
|
5873
5917
|
let measuring = [];
|
|
@@ -5883,7 +5927,7 @@ class EditorView {
|
|
|
5883
5927
|
return BadMeasure;
|
|
5884
5928
|
}
|
|
5885
5929
|
});
|
|
5886
|
-
let update = new ViewUpdate(this, this.state);
|
|
5930
|
+
let update = new ViewUpdate(this, this.state), redrawn = false;
|
|
5887
5931
|
update.flags |= changed;
|
|
5888
5932
|
if (!updated)
|
|
5889
5933
|
updated = update;
|
|
@@ -5893,14 +5937,15 @@ class EditorView {
|
|
|
5893
5937
|
if (!update.empty) {
|
|
5894
5938
|
this.updatePlugins(update);
|
|
5895
5939
|
this.inputState.update(update);
|
|
5940
|
+
this.updateAttrs();
|
|
5941
|
+
redrawn = this.docView.update(update);
|
|
5896
5942
|
}
|
|
5897
|
-
this.updateAttrs();
|
|
5898
|
-
if (changed)
|
|
5899
|
-
this.docView.update(update);
|
|
5900
5943
|
for (let i = 0; i < measuring.length; i++)
|
|
5901
5944
|
if (measured[i] != BadMeasure) {
|
|
5902
5945
|
try {
|
|
5903
|
-
measuring[i]
|
|
5946
|
+
let m = measuring[i];
|
|
5947
|
+
if (m.write)
|
|
5948
|
+
m.write(measured[i], this);
|
|
5904
5949
|
}
|
|
5905
5950
|
catch (e) {
|
|
5906
5951
|
logException(this.state, e);
|
|
@@ -5910,6 +5955,8 @@ class EditorView {
|
|
|
5910
5955
|
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
5911
5956
|
this.viewState.scrollTarget = null;
|
|
5912
5957
|
}
|
|
5958
|
+
if (redrawn)
|
|
5959
|
+
this.docView.updateSelection(true);
|
|
5913
5960
|
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
|
|
5914
5961
|
break;
|
|
5915
5962
|
}
|
|
@@ -5934,8 +5981,6 @@ class EditorView {
|
|
|
5934
5981
|
let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
|
|
5935
5982
|
class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
|
|
5936
5983
|
});
|
|
5937
|
-
updateAttrs(this.dom, this.editorAttrs, editorAttrs);
|
|
5938
|
-
this.editorAttrs = editorAttrs;
|
|
5939
5984
|
let contentAttrs = {
|
|
5940
5985
|
spellcheck: "false",
|
|
5941
5986
|
autocorrect: "off",
|
|
@@ -5950,7 +5995,11 @@ class EditorView {
|
|
|
5950
5995
|
if (this.state.readOnly)
|
|
5951
5996
|
contentAttrs["aria-readonly"] = "true";
|
|
5952
5997
|
combineAttrs(this.state.facet(contentAttributes), contentAttrs);
|
|
5953
|
-
|
|
5998
|
+
this.observer.ignore(() => {
|
|
5999
|
+
updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
|
|
6000
|
+
updateAttrs(this.dom, this.editorAttrs, editorAttrs);
|
|
6001
|
+
});
|
|
6002
|
+
this.editorAttrs = editorAttrs;
|
|
5954
6003
|
this.contentAttrs = contentAttrs;
|
|
5955
6004
|
}
|
|
5956
6005
|
showAnnouncements(trs) {
|
|
@@ -6020,6 +6069,20 @@ class EditorView {
|
|
|
6020
6069
|
return null;
|
|
6021
6070
|
}
|
|
6022
6071
|
/**
|
|
6072
|
+
The top position of the document, in screen coordinates. This
|
|
6073
|
+
may be negative when the editor is scrolled down. Points
|
|
6074
|
+
directly to the top of the first line, not above the padding.
|
|
6075
|
+
*/
|
|
6076
|
+
get documentTop() {
|
|
6077
|
+
return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
|
|
6078
|
+
}
|
|
6079
|
+
/**
|
|
6080
|
+
Reports the padding above and below the document.
|
|
6081
|
+
*/
|
|
6082
|
+
get documentPadding() {
|
|
6083
|
+
return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
|
|
6084
|
+
}
|
|
6085
|
+
/**
|
|
6023
6086
|
Find the line or block widget at the given vertical position.
|
|
6024
6087
|
|
|
6025
6088
|
By default, this position is interpreted as a screen position,
|
|
@@ -6029,10 +6092,21 @@ class EditorView {
|
|
|
6029
6092
|
position, or a precomputed document top
|
|
6030
6093
|
(`view.contentDOM.getBoundingClientRect().top`) to limit layout
|
|
6031
6094
|
queries.
|
|
6095
|
+
|
|
6096
|
+
*Deprecated: use `blockAtHeight` instead.*
|
|
6032
6097
|
*/
|
|
6033
6098
|
blockAtHeight(height, docTop) {
|
|
6099
|
+
let top = ensureTop(docTop, this);
|
|
6100
|
+
return this.elementAtHeight(height - top).moveY(top);
|
|
6101
|
+
}
|
|
6102
|
+
/**
|
|
6103
|
+
Find the text line or block widget at the given vertical
|
|
6104
|
+
position (which is interpreted as relative to the [top of the
|
|
6105
|
+
document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)
|
|
6106
|
+
*/
|
|
6107
|
+
elementAtHeight(height) {
|
|
6034
6108
|
this.readMeasured();
|
|
6035
|
-
return this.viewState.
|
|
6109
|
+
return this.viewState.elementAtHeight(height);
|
|
6036
6110
|
}
|
|
6037
6111
|
/**
|
|
6038
6112
|
Find information for the visual line (see
|
|
@@ -6044,20 +6118,43 @@ class EditorView {
|
|
|
6044
6118
|
Defaults to treating `height` as a screen position. See
|
|
6045
6119
|
[`blockAtHeight`](https://codemirror.net/6/docs/ref/#view.EditorView.blockAtHeight) for the
|
|
6046
6120
|
interpretation of the `docTop` parameter.
|
|
6121
|
+
|
|
6122
|
+
*Deprecated: use `lineBlockAtHeight` instead.*
|
|
6047
6123
|
*/
|
|
6048
6124
|
visualLineAtHeight(height, docTop) {
|
|
6125
|
+
let top = ensureTop(docTop, this);
|
|
6126
|
+
return this.lineBlockAtHeight(height - top).moveY(top);
|
|
6127
|
+
}
|
|
6128
|
+
/**
|
|
6129
|
+
Find the line block (see
|
|
6130
|
+
[`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
|
|
6131
|
+
height.
|
|
6132
|
+
*/
|
|
6133
|
+
lineBlockAtHeight(height) {
|
|
6049
6134
|
this.readMeasured();
|
|
6050
|
-
return this.viewState.
|
|
6135
|
+
return this.viewState.lineBlockAtHeight(height);
|
|
6051
6136
|
}
|
|
6052
6137
|
/**
|
|
6053
6138
|
Iterate over the height information of the visual lines in the
|
|
6054
6139
|
viewport. The heights of lines are reported relative to the
|
|
6055
6140
|
given document top, which defaults to the screen position of the
|
|
6056
6141
|
document (forcing a layout).
|
|
6142
|
+
|
|
6143
|
+
*Deprecated: use `viewportLineBlocks` instead.*
|
|
6057
6144
|
*/
|
|
6058
6145
|
viewportLines(f, docTop) {
|
|
6059
|
-
let
|
|
6060
|
-
|
|
6146
|
+
let top = ensureTop(docTop, this);
|
|
6147
|
+
for (let line of this.viewportLineBlocks)
|
|
6148
|
+
f(line.moveY(top));
|
|
6149
|
+
}
|
|
6150
|
+
/**
|
|
6151
|
+
Get the extent and vertical position of all [line
|
|
6152
|
+
blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
|
|
6153
|
+
are relative to the [top of the
|
|
6154
|
+
document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
|
|
6155
|
+
*/
|
|
6156
|
+
get viewportLineBlocks() {
|
|
6157
|
+
return this.viewState.viewportLines;
|
|
6061
6158
|
}
|
|
6062
6159
|
/**
|
|
6063
6160
|
Find the extent and height of the visual line (a range delimited
|
|
@@ -6068,9 +6165,22 @@ class EditorView {
|
|
|
6068
6165
|
argument, which defaults to 0 for this method. You can pass
|
|
6069
6166
|
`view.contentDOM.getBoundingClientRect().top` here to get screen
|
|
6070
6167
|
coordinates.
|
|
6168
|
+
|
|
6169
|
+
*Deprecated: use `lineBlockAt` instead.*
|
|
6071
6170
|
*/
|
|
6072
6171
|
visualLineAt(pos, docTop = 0) {
|
|
6073
|
-
return this.
|
|
6172
|
+
return this.lineBlockAt(pos).moveY(docTop + this.viewState.paddingTop);
|
|
6173
|
+
}
|
|
6174
|
+
/**
|
|
6175
|
+
Find the line block around the given document position. A line
|
|
6176
|
+
block is a range delimited on both sides by either a
|
|
6177
|
+
non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range) line breaks, or the
|
|
6178
|
+
start/end of the document. It will usually just hold a line of
|
|
6179
|
+
text, but may be broken into multiple textblocks by block
|
|
6180
|
+
widgets.
|
|
6181
|
+
*/
|
|
6182
|
+
lineBlockAt(pos) {
|
|
6183
|
+
return this.viewState.lineBlockAt(pos);
|
|
6074
6184
|
}
|
|
6075
6185
|
/**
|
|
6076
6186
|
The editor's total content height.
|
|
@@ -6403,8 +6513,9 @@ search match).
|
|
|
6403
6513
|
EditorView.announce = state.StateEffect.define();
|
|
6404
6514
|
// Maximum line length for which we compute accurate bidi info
|
|
6405
6515
|
const MaxBidiLine = 4096;
|
|
6406
|
-
|
|
6407
|
-
|
|
6516
|
+
// FIXME remove this and its callers on next breaking release
|
|
6517
|
+
function ensureTop(given, view) {
|
|
6518
|
+
return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
|
|
6408
6519
|
}
|
|
6409
6520
|
let resizeDebounce = -1;
|
|
6410
6521
|
function ensureGlobalHandler() {
|
|
@@ -6755,7 +6866,7 @@ function wrappedLine(view, pos, inside) {
|
|
|
6755
6866
|
type: exports.BlockType.Text };
|
|
6756
6867
|
}
|
|
6757
6868
|
function blockAt(view, pos) {
|
|
6758
|
-
let line = view.
|
|
6869
|
+
let line = view.lineBlockAt(pos);
|
|
6759
6870
|
if (Array.isArray(line.type))
|
|
6760
6871
|
for (let l of line.type) {
|
|
6761
6872
|
if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
|
|
@@ -7146,7 +7257,7 @@ const activeLineHighlighter = ViewPlugin.fromClass(class {
|
|
|
7146
7257
|
for (let r of view.state.selection.ranges) {
|
|
7147
7258
|
if (!r.empty)
|
|
7148
7259
|
return Decoration.none;
|
|
7149
|
-
let line = view.
|
|
7260
|
+
let line = view.lineBlockAt(r.head);
|
|
7150
7261
|
if (line.from > lastLineStart) {
|
|
7151
7262
|
deco.push(lineDeco.range(line.from));
|
|
7152
7263
|
lastLineStart = line.from;
|