@codemirror/view 0.19.19 → 0.19.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -186,7 +186,7 @@ function scrollRectIntoView(dom, rect, side, center) {
186
186
  }
187
187
  }
188
188
  }
189
- class DOMSelection {
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
- set(domSel) {
201
- this.anchorNode = domSel.anchorNode;
202
- this.anchorOffset = domSel.anchorOffset;
203
- this.focusNode = domSel.focusNode;
204
- this.focusOffset = domSel.focusOffset;
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.transactions.some(tr => tr.docChanged);
1915
+ return !this.changes.empty;
1913
1916
  }
1914
1917
  /**
1915
1918
  Whether the selection was explicitly set in this update.
@@ -1943,6 +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;
1950
+ // Used by the resize observer to ignore resizes that we caused
1951
+ // ourselves
1952
+ this.lastUpdate = Date.now();
1946
1953
  this.setDOM(view.contentDOM);
1947
1954
  this.children = [new LineView];
1948
1955
  this.children[0].setParent(this);
@@ -1975,21 +1982,19 @@ class DocView extends ContentView {
1975
1982
  // getSelection than the one that it actually shows to the user.
1976
1983
  // This forces a selection update when lines are joined to work
1977
1984
  // around that. Issue #54
1978
- let forceSelection = (browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1979
- 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;
1980
1988
  let prevDeco = this.decorations, deco = this.updateDeco();
1981
1989
  let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1982
1990
  changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1983
- let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
1984
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
1985
- !(update.flags & 4 /* Viewport */) &&
1986
- update.state.selection.main.from >= this.view.viewport.from &&
1987
- update.state.selection.main.to <= this.view.viewport.to) {
1988
- this.updateSelection(forceSelection, pointerSel);
1991
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
1989
1992
  return false;
1990
1993
  }
1991
1994
  else {
1992
- this.updateInner(changedRanges, deco, update.startState.doc.length, forceSelection, pointerSel);
1995
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
1996
+ if (update.transactions.length)
1997
+ this.lastUpdate = Date.now();
1993
1998
  return true;
1994
1999
  }
1995
2000
  }
@@ -1997,13 +2002,16 @@ class DocView extends ContentView {
1997
2002
  if (this.dirty) {
1998
2003
  this.view.observer.ignore(() => this.view.docView.sync());
1999
2004
  this.dirty = 0 /* Not */;
2005
+ this.updateSelection(true);
2000
2006
  }
2001
- if (sel)
2007
+ else {
2002
2008
  this.updateSelection();
2009
+ }
2003
2010
  }
2004
2011
  // Used both by update and checkLayout do perform the actual DOM
2005
2012
  // update
2006
- updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
2013
+ updateInner(changes, deco, oldLength) {
2014
+ this.view.viewState.mustMeasureContent = true;
2007
2015
  this.updateChildren(changes, deco, oldLength);
2008
2016
  let { observer } = this.view;
2009
2017
  observer.ignore(() => {
@@ -2011,7 +2019,7 @@ class DocView extends ContentView {
2011
2019
  // messes with the scroll position during DOM mutation (though
2012
2020
  // no relayout is triggered and I cannot imagine how it can
2013
2021
  // recompute the scroll position without a layout)
2014
- this.dom.style.height = this.view.viewState.domHeight + "px";
2022
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2015
2023
  this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2016
2024
  // Chrome will sometimes, when DOM mutations occur directly
2017
2025
  // around the selection, get confused and report a different
@@ -2021,8 +2029,7 @@ class DocView extends ContentView {
2021
2029
  this.sync(track);
2022
2030
  this.dirty = 0 /* Not */;
2023
2031
  if (track && (track.written || observer.selectionRange.focusNode != track.node))
2024
- forceSelection = true;
2025
- this.updateSelection(forceSelection, pointerSel);
2032
+ this.forceSelection = true;
2026
2033
  this.dom.style.height = "";
2027
2034
  });
2028
2035
  let gaps = [];
@@ -2108,10 +2115,14 @@ class DocView extends ContentView {
2108
2115
  this.replaceChildren(fromI, toI, content);
2109
2116
  }
2110
2117
  // Sync the DOM selection to this.state.selection
2111
- updateSelection(force = false, fromPointer = false) {
2118
+ updateSelection(mustRead = false, fromPointer = false) {
2119
+ if (mustRead)
2120
+ this.view.observer.readSelectionRange();
2112
2121
  if (!(fromPointer || this.mayControlSelection()) ||
2113
2122
  browser.ios && this.view.inputState.rapidCompositionStart)
2114
2123
  return;
2124
+ let force = this.forceSelection;
2125
+ this.forceSelection = false;
2115
2126
  let main = this.view.state.selection.main;
2116
2127
  // FIXME need to handle the case where the selection falls inside a block range
2117
2128
  let anchor = this.domAtPos(main.anchor);
@@ -2293,7 +2304,7 @@ class DocView extends ContentView {
2293
2304
  let next = i == vs.viewports.length ? null : vs.viewports[i];
2294
2305
  let end = next ? next.from - 1 : this.length;
2295
2306
  if (end > pos) {
2296
- let height = vs.lineAt(end, 0).bottom - vs.lineAt(pos, 0).top;
2307
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2297
2308
  deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2298
2309
  }
2299
2310
  if (!next)
@@ -2900,13 +2911,14 @@ function domPosInText(node, x, y) {
2900
2911
  }
2901
2912
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2902
2913
  var _a;
2903
- let content = view.contentDOM.getBoundingClientRect(), block;
2914
+ let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2904
2915
  let halfLine = view.defaultLineHeight / 2;
2916
+ let block, yOffset = y - docTop;
2905
2917
  for (let bounced = false;;) {
2906
- block = view.blockAtHeight(y, content.top);
2907
- if (block.top > y || block.bottom < y) {
2908
- bias = block.top > y ? -1 : 1;
2909
- y = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, y));
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));
2910
2922
  if (bounced)
2911
2923
  return precise ? null : 0;
2912
2924
  else
@@ -2914,8 +2926,9 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2914
2926
  }
2915
2927
  if (block.type == exports.BlockType.Text)
2916
2928
  break;
2917
- y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2929
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2918
2930
  }
2931
+ y = docTop + yOffset;
2919
2932
  let lineStart = block.from;
2920
2933
  // Clip x to the viewport sides
2921
2934
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
@@ -3028,17 +3041,17 @@ function moveVertically(view, start, forward, distance) {
3028
3041
  return state.EditorSelection.cursor(startPos);
3029
3042
  let goal = start.goalColumn, startY;
3030
3043
  let rect = view.contentDOM.getBoundingClientRect();
3031
- let startCoords = view.coordsAtPos(startPos);
3044
+ let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
3032
3045
  if (startCoords) {
3033
3046
  if (goal == null)
3034
3047
  goal = startCoords.left - rect.left;
3035
3048
  startY = dir < 0 ? startCoords.top : startCoords.bottom;
3036
3049
  }
3037
3050
  else {
3038
- let line = view.viewState.lineAt(startPos, view.dom.getBoundingClientRect().top);
3051
+ let line = view.viewState.lineBlockAt(startPos - docTop);
3039
3052
  if (goal == null)
3040
3053
  goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3041
- startY = dir < 0 ? line.top : line.bottom;
3054
+ startY = (dir < 0 ? line.top : line.bottom) + docTop;
3042
3055
  }
3043
3056
  let resolvedGoal = rect.left + goal;
3044
3057
  let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
@@ -3289,7 +3302,7 @@ class MouseSelection {
3289
3302
  this.extend = startEvent.shiftKey;
3290
3303
  this.multiple = view.state.facet(state.EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
3291
3304
  this.dragMove = dragMovesSelection(view, startEvent);
3292
- this.dragging = isInPrimarySelection(view, startEvent) ? null : false;
3305
+ this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
3293
3306
  // When clicking outside of the selection, immediately apply the
3294
3307
  // effect of starting the selection
3295
3308
  if (this.dragging === false) {
@@ -3510,7 +3523,7 @@ function basicMouseSelection(view, event) {
3510
3523
  let last = start, lastEvent = event;
3511
3524
  return {
3512
3525
  update(update) {
3513
- if (update.changes) {
3526
+ if (update.docChanged) {
3514
3527
  if (start)
3515
3528
  start.pos = update.changes.mapPos(start.pos);
3516
3529
  startSel = startSel.map(update.changes);
@@ -3774,7 +3787,10 @@ class HeightOracle {
3774
3787
  return lines * this.lineHeight;
3775
3788
  }
3776
3789
  setDoc(doc) { this.doc = doc; return this; }
3777
- mustRefresh(lineHeights, whiteSpace, direction) {
3790
+ mustRefreshForStyle(whiteSpace, direction) {
3791
+ return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3792
+ }
3793
+ mustRefreshForHeights(lineHeights) {
3778
3794
  let newHeight = false;
3779
3795
  for (let i = 0; i < lineHeights.length; i++) {
3780
3796
  let h = lineHeights[i];
@@ -3786,7 +3802,7 @@ class HeightOracle {
3786
3802
  this.heightSamples[Math.floor(h * 10)] = true;
3787
3803
  }
3788
3804
  }
3789
- return newHeight || (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3805
+ return newHeight;
3790
3806
  }
3791
3807
  refresh(whiteSpace, direction, lineHeight, charWidth, lineLength, knownHeights) {
3792
3808
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
@@ -3840,7 +3856,8 @@ class BlockInfo {
3840
3856
  */
3841
3857
  length,
3842
3858
  /**
3843
- The top position of the element.
3859
+ The top position of the element (relative to the top of the
3860
+ document).
3844
3861
  */
3845
3862
  top,
3846
3863
  /**
@@ -3874,6 +3891,12 @@ class BlockInfo {
3874
3891
  .concat(Array.isArray(other.type) ? other.type : [other]);
3875
3892
  return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, detail);
3876
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
+ }
3877
3900
  }
3878
3901
  var QueryType;
3879
3902
  (function (QueryType) {
@@ -3881,7 +3904,7 @@ var QueryType;
3881
3904
  QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
3882
3905
  QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
3883
3906
  })(QueryType || (QueryType = {}));
3884
- const Epsilon = 1e-4;
3907
+ const Epsilon = 1e-3;
3885
3908
  class HeightMap {
3886
3909
  constructor(length, // The number of characters covered
3887
3910
  height, // Height of this part of the document
@@ -4108,22 +4131,30 @@ class HeightMapGap extends HeightMap {
4108
4131
  // can't be widgets or collapsed ranges in those lines, because
4109
4132
  // they would already have been added to the heightmap (gaps
4110
4133
  // only contain plain text).
4111
- let nodes = [], pos = Math.max(offset, measured.from);
4134
+ let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
4135
+ let wasChanged = oracle.heightChanged;
4112
4136
  if (measured.from > offset)
4113
4137
  nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
4114
4138
  while (pos <= end && measured.more) {
4115
4139
  let len = oracle.doc.lineAt(pos).length;
4116
4140
  if (nodes.length)
4117
4141
  nodes.push(null);
4118
- let line = new HeightMapText(len, measured.heights[measured.index++]);
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);
4119
4148
  line.outdated = false;
4120
4149
  nodes.push(line);
4121
4150
  pos += len + 1;
4122
4151
  }
4123
4152
  if (pos <= end)
4124
4153
  nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
4125
- oracle.heightChanged = true;
4126
- return HeightMap.of(nodes);
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;
4127
4158
  }
4128
4159
  else if (force || this.outdated) {
4129
4160
  this.setHeight(oracle, oracle.heightForGap(offset, offset + this.length));
@@ -4481,13 +4512,18 @@ class ViewState {
4481
4512
  this.inView = true;
4482
4513
  this.paddingTop = 0;
4483
4514
  this.paddingBottom = 0;
4484
- this.contentWidth = 0;
4515
+ this.contentDOMWidth = 0;
4516
+ this.contentDOMHeight = 0;
4517
+ this.editorHeight = 0;
4485
4518
  this.heightOracle = new HeightOracle;
4486
4519
  // See VP.MaxDOMHeight
4487
4520
  this.scaler = IdScaler;
4488
4521
  this.scrollTarget = null;
4489
4522
  // Briefly set to true when printing, to disable viewport limiting
4490
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;
4491
4527
  this.visibleRanges = [];
4492
4528
  // Cursor 'assoc' is only significant when the cursor is on a line
4493
4529
  // wrap point, where it must stick to the character that it is
@@ -4500,6 +4536,7 @@ class ViewState {
4500
4536
  this.mustEnforceCursorAssoc = false;
4501
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)]);
4502
4538
  this.viewport = this.getViewport(0, null);
4539
+ this.updateViewportLines();
4503
4540
  this.updateForViewport();
4504
4541
  this.lineGaps = this.ensureLineGaps([]);
4505
4542
  this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(false)));
@@ -4510,7 +4547,7 @@ class ViewState {
4510
4547
  for (let i = 0; i <= 1; i++) {
4511
4548
  let pos = i ? main.head : main.anchor;
4512
4549
  if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
4513
- let { from, to } = this.lineAt(pos, 0);
4550
+ let { from, to } = this.lineBlockAt(pos);
4514
4551
  viewports.push(new Viewport(from, to));
4515
4552
  }
4516
4553
  }
@@ -4518,6 +4555,12 @@ class ViewState {
4518
4555
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4519
4556
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4520
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
+ }
4521
4564
  update(update, scrollTarget = null) {
4522
4565
  let prev = this.state;
4523
4566
  this.state = update.state;
@@ -4532,7 +4575,11 @@ class ViewState {
4532
4575
  if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4533
4576
  !this.viewportIsAppropriate(viewport))
4534
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;
4535
4580
  this.viewport = viewport;
4581
+ if (updateLines)
4582
+ this.updateViewportLines();
4536
4583
  this.updateForViewport();
4537
4584
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4538
4585
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
@@ -4543,13 +4590,17 @@ class ViewState {
4543
4590
  update.state.selection.main.empty && update.state.selection.main.assoc)
4544
4591
  this.mustEnforceCursorAssoc = true;
4545
4592
  }
4546
- measure(docView, repeated) {
4547
- let dom = docView.dom, whiteSpace = "", direction = exports.Direction.LTR;
4548
- let result = 0;
4549
- if (!repeated) {
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;
4550
4603
  // Vertical padding
4551
- let style = window.getComputedStyle(dom);
4552
- whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
4553
4604
  let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4554
4605
  if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4555
4606
  result |= 8 /* Geometry */;
@@ -4564,35 +4615,42 @@ class ViewState {
4564
4615
  this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4565
4616
  if (!this.inView)
4566
4617
  return 0;
4567
- let lineHeights = docView.measureVisibleLineHeights();
4568
- let refresh = false, bias = 0, oracle = this.heightOracle;
4569
- if (!repeated) {
4570
- let contentWidth = docView.dom.clientWidth;
4571
- if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
4572
- oracle.lineWrapping && Math.abs(contentWidth - this.contentWidth) > oracle.charWidth) {
4573
- 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();
4574
4625
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4575
4626
  if (refresh) {
4576
- docView.minWidth = 0;
4627
+ view.docView.minWidth = 0;
4577
4628
  result |= 8 /* Geometry */;
4578
4629
  }
4579
4630
  }
4580
- if (this.contentWidth != contentWidth) {
4581
- this.contentWidth = contentWidth;
4631
+ if (this.contentDOMWidth != contentWidth) {
4632
+ this.contentDOMWidth = contentWidth;
4633
+ result |= 8 /* Geometry */;
4634
+ }
4635
+ if (this.editorHeight != view.scrollDOM.clientHeight) {
4636
+ this.editorHeight = view.scrollDOM.clientHeight;
4582
4637
  result |= 8 /* Geometry */;
4583
4638
  }
4584
4639
  if (dTop > 0 && dBottom > 0)
4585
4640
  bias = Math.max(dTop, dBottom);
4586
4641
  else if (dTop < 0 && dBottom < 0)
4587
4642
  bias = Math.min(dTop, dBottom);
4588
- }
4589
- oracle.heightChanged = false;
4590
- this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4591
- if (oracle.heightChanged)
4592
- result |= 2 /* Height */;
4593
- if (!this.viewportIsAppropriate(this.viewport, bias) ||
4594
- 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)
4595
4651
  this.viewport = this.getViewport(bias, this.scrollTarget);
4652
+ if ((result & 2 /* Height */) || viewportChange)
4653
+ this.updateViewportLines();
4596
4654
  this.updateForViewport();
4597
4655
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4598
4656
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
@@ -4603,12 +4661,12 @@ class ViewState {
4603
4661
  // to a line end is going to trigger a layout anyway, so it
4604
4662
  // can't be a pure write. It should be rare that it does any
4605
4663
  // writing.
4606
- docView.enforceCursorAssoc();
4664
+ view.docView.enforceCursorAssoc();
4607
4665
  }
4608
4666
  return result;
4609
4667
  }
4610
- get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4611
- get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4668
+ get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
4669
+ get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
4612
4670
  getViewport(bias, scrollTarget) {
4613
4671
  // This will divide VP.Margin between the top and the
4614
4672
  // bottom, depending on the bias (the change in viewport position
@@ -4668,12 +4726,12 @@ class ViewState {
4668
4726
  // This won't work at all in predominantly right-to-left text.
4669
4727
  if (this.heightOracle.direction != exports.Direction.LTR)
4670
4728
  return gaps;
4671
- this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4729
+ for (let line of this.viewportLines) {
4672
4730
  if (line.length < 4000 /* DoubleMargin */)
4673
- return;
4731
+ continue;
4674
4732
  let structure = lineStructure(line.from, line.to, this.state);
4675
4733
  if (structure.total < 4000 /* DoubleMargin */)
4676
- return;
4734
+ continue;
4677
4735
  let viewFrom, viewTo;
4678
4736
  if (this.heightOracle.lineWrapping) {
4679
4737
  let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
@@ -4703,7 +4761,7 @@ class ViewState {
4703
4761
  Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4704
4762
  new LineGap(from, to, this.gapSize(line, from, to, structure)));
4705
4763
  }
4706
- });
4764
+ }
4707
4765
  return gaps;
4708
4766
  }
4709
4767
  gapSize(line, from, to, structure) {
@@ -4735,27 +4793,18 @@ class ViewState {
4735
4793
  this.visibleRanges = ranges;
4736
4794
  return changed ? 4 /* Viewport */ : 0;
4737
4795
  }
4738
- lineAt(pos, editorTop) {
4739
- editorTop += this.paddingTop;
4740
- return scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, editorTop, 0), this.scaler, editorTop);
4741
- }
4742
- lineAtHeight(height, editorTop) {
4743
- editorTop += this.paddingTop;
4744
- return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height, editorTop), QueryType.ByHeight, this.state.doc, editorTop, 0), this.scaler, editorTop);
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);
4745
4799
  }
4746
- blockAtHeight(height, editorTop) {
4747
- editorTop += this.paddingTop;
4748
- return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height, editorTop), 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);
4749
4802
  }
4750
- forEachLine(from, to, f, editorTop) {
4751
- editorTop += this.paddingTop;
4752
- 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);
4753
4805
  }
4754
4806
  get contentHeight() {
4755
- return this.domHeight + this.paddingTop + this.paddingBottom;
4756
- }
4757
- get domHeight() {
4758
- return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4807
+ return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4759
4808
  }
4760
4809
  }
4761
4810
  class Viewport {
@@ -4852,36 +4901,34 @@ class BigScaler {
4852
4901
  base = obj.bottom;
4853
4902
  }
4854
4903
  }
4855
- toDOM(n, top) {
4856
- n -= top;
4904
+ toDOM(n) {
4857
4905
  for (let i = 0, base = 0, domBase = 0;; i++) {
4858
4906
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4859
4907
  if (!vp || n < vp.top)
4860
- return domBase + (n - base) * this.scale + top;
4908
+ return domBase + (n - base) * this.scale;
4861
4909
  if (n <= vp.bottom)
4862
- return vp.domTop + (n - vp.top) + top;
4910
+ return vp.domTop + (n - vp.top);
4863
4911
  base = vp.bottom;
4864
4912
  domBase = vp.domBottom;
4865
4913
  }
4866
4914
  }
4867
- fromDOM(n, top) {
4868
- n -= top;
4915
+ fromDOM(n) {
4869
4916
  for (let i = 0, base = 0, domBase = 0;; i++) {
4870
4917
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4871
4918
  if (!vp || n < vp.domTop)
4872
- return base + (n - domBase) / this.scale + top;
4919
+ return base + (n - domBase) / this.scale;
4873
4920
  if (n <= vp.domBottom)
4874
- return vp.top + (n - vp.domTop) + top;
4921
+ return vp.top + (n - vp.domTop);
4875
4922
  base = vp.bottom;
4876
4923
  domBase = vp.domBottom;
4877
4924
  }
4878
4925
  }
4879
4926
  }
4880
- function scaleBlock(block, scaler, top) {
4927
+ function scaleBlock(block, scaler) {
4881
4928
  if (scaler.scale == 1)
4882
4929
  return block;
4883
- let bTop = scaler.toDOM(block.top, top), bBottom = scaler.toDOM(block.bottom, top);
4884
- return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler, top)) : block.type);
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);
4885
4932
  }
4886
4933
 
4887
4934
  const theme = state.Facet.define({ combine: strs => strs.join(" ") });
@@ -5066,24 +5113,30 @@ class DOMObserver {
5066
5113
  this.onChange = onChange;
5067
5114
  this.onScrollChanged = onScrollChanged;
5068
5115
  this.active = false;
5069
- this.ignoreSelection = new DOMSelection;
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;
5070
5125
  this.delayedFlush = -1;
5126
+ this.resizeTimeout = -1;
5071
5127
  this.queue = [];
5072
- this.lastFlush = 0;
5073
5128
  this.scrollTargets = [];
5074
5129
  this.intersection = null;
5130
+ this.resize = null;
5075
5131
  this.intersecting = false;
5076
5132
  this.gapIntersection = null;
5077
5133
  this.gaps = [];
5078
- // Used to work around a Safari Selection/shadow DOM bug (#414)
5079
- this._selectionRange = null;
5080
5134
  // Timeout for scheduling check of the parents that need scroll handlers
5081
5135
  this.parentCheck = -1;
5082
5136
  this.dom = view.contentDOM;
5083
5137
  this.observer = new MutationObserver(mutations => {
5084
5138
  for (let mut of mutations)
5085
5139
  this.queue.push(mut);
5086
- this._selectionRange = null;
5087
5140
  // IE11 will sometimes (on typing over a selection or
5088
5141
  // backspacing out a single character text node) call the
5089
5142
  // observer callback before actually updating the DOM.
@@ -5108,6 +5161,16 @@ class DOMObserver {
5108
5161
  this.flushSoon();
5109
5162
  };
5110
5163
  this.onSelectionChange = this.onSelectionChange.bind(this);
5164
+ if (typeof ResizeObserver == "function") {
5165
+ this.resize = new ResizeObserver(() => {
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);
5171
+ });
5172
+ this.resize.observe(view.scrollDOM);
5173
+ }
5111
5174
  this.start();
5112
5175
  this.onScroll = this.onScroll.bind(this);
5113
5176
  window.addEventListener("scroll", this.onScroll);
@@ -5128,10 +5191,12 @@ class DOMObserver {
5128
5191
  }, {});
5129
5192
  }
5130
5193
  this.listenForScroll();
5194
+ this.readSelectionRange();
5195
+ this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5131
5196
  }
5132
5197
  onScroll(e) {
5133
5198
  if (this.intersecting)
5134
- this.flush();
5199
+ this.flush(false);
5135
5200
  this.onScrollChanged(e);
5136
5201
  }
5137
5202
  updateGaps(gaps) {
@@ -5143,8 +5208,8 @@ class DOMObserver {
5143
5208
  }
5144
5209
  }
5145
5210
  onSelectionChange(event) {
5146
- if (this.lastFlush < Date.now() - 50)
5147
- this._selectionRange = null;
5211
+ if (!this.readSelectionRange())
5212
+ return;
5148
5213
  let { view } = this, sel = this.selectionRange;
5149
5214
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
5150
5215
  return;
@@ -5159,24 +5224,22 @@ class DOMObserver {
5159
5224
  sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
5160
5225
  this.flushSoon();
5161
5226
  else
5162
- this.flush();
5163
- }
5164
- get selectionRange() {
5165
- if (!this._selectionRange) {
5166
- let { root } = this.view, sel = getSelection(root);
5167
- // The Selection object is broken in shadow roots in Safari. See
5168
- // https://github.com/codemirror/codemirror.next/issues/414
5169
- if (browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM)
5170
- sel = safariSelectionRangeHack(this.view) || sel;
5171
- this._selectionRange = sel;
5172
- }
5173
- return this._selectionRange;
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;
5174
5239
  }
5175
5240
  setSelectionRange(anchor, head) {
5176
- var _a;
5177
- if (!((_a = this._selectionRange) === null || _a === void 0 ? void 0 : _a.type))
5178
- this._selectionRange = { anchorNode: anchor.node, anchorOffset: anchor.offset,
5179
- focusNode: head.node, focusOffset: head.offset };
5241
+ this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
5242
+ this.selectionChanged = false;
5180
5243
  }
5181
5244
  listenForScroll() {
5182
5245
  this.parentCheck = -1;
@@ -5223,7 +5286,6 @@ class DOMObserver {
5223
5286
  if (this.active)
5224
5287
  return;
5225
5288
  this.observer.observe(this.dom, observeOptions);
5226
- this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5227
5289
  if (useCharData)
5228
5290
  this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
5229
5291
  this.active = true;
@@ -5233,18 +5295,14 @@ class DOMObserver {
5233
5295
  return;
5234
5296
  this.active = false;
5235
5297
  this.observer.disconnect();
5236
- this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5237
5298
  if (useCharData)
5238
5299
  this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
5239
5300
  }
5240
- clearSelection() {
5241
- this.ignoreSelection.set(this.selectionRange);
5242
- }
5243
5301
  // Throw away any pending changes
5244
5302
  clear() {
5245
5303
  this.observer.takeRecords();
5246
5304
  this.queue.length = 0;
5247
- this.clearSelection();
5305
+ this.selectionChanged = false;
5248
5306
  }
5249
5307
  flushSoon() {
5250
5308
  if (this.delayedFlush < 0)
@@ -5281,24 +5339,24 @@ class DOMObserver {
5281
5339
  return { from, to, typeOver };
5282
5340
  }
5283
5341
  // Apply pending changes, if any
5284
- flush() {
5342
+ flush(readSelection = true) {
5343
+ if (readSelection)
5344
+ this.readSelectionRange();
5285
5345
  // Completely hold off flushing when pending keys are set—the code
5286
5346
  // managing those will make sure processRecords is called and the
5287
5347
  // view is resynchronized after
5288
5348
  if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5289
5349
  return;
5290
- this.lastFlush = Date.now();
5291
5350
  let { from, to, typeOver } = this.processRecords();
5292
- let selection = this.selectionRange;
5293
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5351
+ let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5294
5352
  if (from < 0 && !newSel)
5295
5353
  return;
5354
+ this.selectionChanged = false;
5296
5355
  let startState = this.view.state;
5297
5356
  this.onChange(from, to, typeOver);
5298
5357
  // The view wasn't updated
5299
5358
  if (this.view.state == startState)
5300
5359
  this.view.docView.reset(newSel);
5301
- this.clearSelection();
5302
5360
  }
5303
5361
  readMutation(rec) {
5304
5362
  let cView = this.view.docView.nearest(rec.target);
@@ -5321,15 +5379,16 @@ class DOMObserver {
5321
5379
  }
5322
5380
  }
5323
5381
  destroy() {
5382
+ var _a, _b, _c;
5324
5383
  this.stop();
5325
- if (this.intersection)
5326
- this.intersection.disconnect();
5327
- if (this.gapIntersection)
5328
- this.gapIntersection.disconnect();
5384
+ (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
5385
+ (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
5386
+ (_c = this.resize) === null || _c === void 0 ? void 0 : _c.disconnect();
5329
5387
  for (let dom of this.scrollTargets)
5330
5388
  dom.removeEventListener("scroll", this.onScroll);
5331
5389
  window.removeEventListener("scroll", this.onScroll);
5332
5390
  clearTimeout(this.parentCheck);
5391
+ clearTimeout(this.resizeTimeout);
5333
5392
  }
5334
5393
  }
5335
5394
  function findChild(cView, dom, dir) {
@@ -5342,6 +5401,7 @@ function findChild(cView, dom, dir) {
5342
5401
  }
5343
5402
  return null;
5344
5403
  }
5404
+ // Used to work around a Safari Selection/shadow DOM bug (#414)
5345
5405
  function safariSelectionRangeHack(view) {
5346
5406
  let found = null;
5347
5407
  // Because Safari (at least in 2018-2021) doesn't provide regular
@@ -5761,6 +5821,7 @@ class EditorView {
5761
5821
  this.mountStyles();
5762
5822
  this.updateAttrs();
5763
5823
  this.showAnnouncements(transactions);
5824
+ this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
5764
5825
  }
5765
5826
  finally {
5766
5827
  this.updateState = 0 /* Idle */;
@@ -5838,7 +5899,7 @@ class EditorView {
5838
5899
  return;
5839
5900
  if (this.measureScheduled > -1)
5840
5901
  cancelAnimationFrame(this.measureScheduled);
5841
- this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
5902
+ this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
5842
5903
  if (flush)
5843
5904
  this.observer.flush();
5844
5905
  let updated = null;
@@ -5846,11 +5907,11 @@ class EditorView {
5846
5907
  for (let i = 0;; i++) {
5847
5908
  this.updateState = 1 /* Measuring */;
5848
5909
  let oldViewport = this.viewport;
5849
- let changed = this.viewState.measure(this.docView, i > 0);
5910
+ let changed = this.viewState.measure(this);
5850
5911
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5851
5912
  break;
5852
5913
  if (i > 5) {
5853
- console.warn("Viewport failed to stabilize");
5914
+ console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5854
5915
  break;
5855
5916
  }
5856
5917
  let measuring = [];
@@ -5866,7 +5927,7 @@ class EditorView {
5866
5927
  return BadMeasure;
5867
5928
  }
5868
5929
  });
5869
- let update = new ViewUpdate(this, this.state);
5930
+ let update = new ViewUpdate(this, this.state), redrawn = false;
5870
5931
  update.flags |= changed;
5871
5932
  if (!updated)
5872
5933
  updated = update;
@@ -5876,14 +5937,15 @@ class EditorView {
5876
5937
  if (!update.empty) {
5877
5938
  this.updatePlugins(update);
5878
5939
  this.inputState.update(update);
5940
+ this.updateAttrs();
5941
+ redrawn = this.docView.update(update);
5879
5942
  }
5880
- this.updateAttrs();
5881
- if (changed)
5882
- this.docView.update(update);
5883
5943
  for (let i = 0; i < measuring.length; i++)
5884
5944
  if (measured[i] != BadMeasure) {
5885
5945
  try {
5886
- measuring[i].write(measured[i], this);
5946
+ let m = measuring[i];
5947
+ if (m.write)
5948
+ m.write(measured[i], this);
5887
5949
  }
5888
5950
  catch (e) {
5889
5951
  logException(this.state, e);
@@ -5893,14 +5955,16 @@ class EditorView {
5893
5955
  this.docView.scrollIntoView(this.viewState.scrollTarget);
5894
5956
  this.viewState.scrollTarget = null;
5895
5957
  }
5958
+ if (redrawn)
5959
+ this.docView.updateSelection(true);
5896
5960
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5897
5961
  break;
5898
5962
  }
5899
5963
  }
5900
5964
  finally {
5901
5965
  this.updateState = 0 /* Idle */;
5966
+ this.measureScheduled = -1;
5902
5967
  }
5903
- this.measureScheduled = -1;
5904
5968
  if (updated && !updated.empty)
5905
5969
  for (let listener of this.state.facet(updateListener))
5906
5970
  listener(updated);
@@ -5917,8 +5981,6 @@ class EditorView {
5917
5981
  let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
5918
5982
  class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
5919
5983
  });
5920
- updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5921
- this.editorAttrs = editorAttrs;
5922
5984
  let contentAttrs = {
5923
5985
  spellcheck: "false",
5924
5986
  autocorrect: "off",
@@ -5933,7 +5995,11 @@ class EditorView {
5933
5995
  if (this.state.readOnly)
5934
5996
  contentAttrs["aria-readonly"] = "true";
5935
5997
  combineAttrs(this.state.facet(contentAttributes), contentAttrs);
5936
- updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5998
+ this.observer.ignore(() => {
5999
+ updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
6000
+ updateAttrs(this.dom, this.editorAttrs, editorAttrs);
6001
+ });
6002
+ this.editorAttrs = editorAttrs;
5937
6003
  this.contentAttrs = contentAttrs;
5938
6004
  }
5939
6005
  showAnnouncements(trs) {
@@ -6003,6 +6069,20 @@ class EditorView {
6003
6069
  return null;
6004
6070
  }
6005
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
+ /**
6006
6086
  Find the line or block widget at the given vertical position.
6007
6087
 
6008
6088
  By default, this position is interpreted as a screen position,
@@ -6012,10 +6092,21 @@ class EditorView {
6012
6092
  position, or a precomputed document top
6013
6093
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6014
6094
  queries.
6095
+
6096
+ *Deprecated: use `blockAtHeight` instead.*
6015
6097
  */
6016
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) {
6017
6108
  this.readMeasured();
6018
- return this.viewState.blockAtHeight(height, ensureTop(docTop, this.contentDOM));
6109
+ return this.viewState.elementAtHeight(height);
6019
6110
  }
6020
6111
  /**
6021
6112
  Find information for the visual line (see
@@ -6027,20 +6118,43 @@ class EditorView {
6027
6118
  Defaults to treating `height` as a screen position. See
6028
6119
  [`blockAtHeight`](https://codemirror.net/6/docs/ref/#view.EditorView.blockAtHeight) for the
6029
6120
  interpretation of the `docTop` parameter.
6121
+
6122
+ *Deprecated: use `lineBlockAtHeight` instead.*
6030
6123
  */
6031
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) {
6032
6134
  this.readMeasured();
6033
- return this.viewState.lineAtHeight(height, ensureTop(docTop, this.contentDOM));
6135
+ return this.viewState.lineBlockAtHeight(height);
6034
6136
  }
6035
6137
  /**
6036
6138
  Iterate over the height information of the visual lines in the
6037
6139
  viewport. The heights of lines are reported relative to the
6038
6140
  given document top, which defaults to the screen position of the
6039
6141
  document (forcing a layout).
6142
+
6143
+ *Deprecated: use `viewportLineBlocks` instead.*
6040
6144
  */
6041
6145
  viewportLines(f, docTop) {
6042
- let { from, to } = this.viewport;
6043
- this.viewState.forEachLine(from, to, f, ensureTop(docTop, this.contentDOM));
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;
6044
6158
  }
6045
6159
  /**
6046
6160
  Find the extent and height of the visual line (a range delimited
@@ -6051,9 +6165,22 @@ class EditorView {
6051
6165
  argument, which defaults to 0 for this method. You can pass
6052
6166
  `view.contentDOM.getBoundingClientRect().top` here to get screen
6053
6167
  coordinates.
6168
+
6169
+ *Deprecated: use `lineBlockAt` instead.*
6054
6170
  */
6055
6171
  visualLineAt(pos, docTop = 0) {
6056
- return this.viewState.lineAt(pos, docTop);
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);
6057
6184
  }
6058
6185
  /**
6059
6186
  The editor's total content height.
@@ -6386,8 +6513,9 @@ search match).
6386
6513
  EditorView.announce = state.StateEffect.define();
6387
6514
  // Maximum line length for which we compute accurate bidi info
6388
6515
  const MaxBidiLine = 4096;
6389
- function ensureTop(given, dom) {
6390
- return given == null ? dom.getBoundingClientRect().top : given;
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;
6391
6519
  }
6392
6520
  let resizeDebounce = -1;
6393
6521
  function ensureGlobalHandler() {
@@ -6738,7 +6866,7 @@ function wrappedLine(view, pos, inside) {
6738
6866
  type: exports.BlockType.Text };
6739
6867
  }
6740
6868
  function blockAt(view, pos) {
6741
- let line = view.visualLineAt(pos);
6869
+ let line = view.lineBlockAt(pos);
6742
6870
  if (Array.isArray(line.type))
6743
6871
  for (let l of line.type) {
6744
6872
  if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
@@ -7129,7 +7257,7 @@ const activeLineHighlighter = ViewPlugin.fromClass(class {
7129
7257
  for (let r of view.state.selection.ranges) {
7130
7258
  if (!r.empty)
7131
7259
  return Decoration.none;
7132
- let line = view.visualLineAt(r.head);
7260
+ let line = view.lineBlockAt(r.head);
7133
7261
  if (line.from > lastLineStart) {
7134
7262
  deco.push(lineDeco.range(line.from));
7135
7263
  lastLineStart = line.from;