@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/dist/index.js CHANGED
@@ -183,7 +183,7 @@ function scrollRectIntoView(dom, rect, side, center) {
183
183
  }
184
184
  }
185
185
  }
186
- class DOMSelection {
186
+ class DOMSelectionState {
187
187
  constructor() {
188
188
  this.anchorNode = null;
189
189
  this.anchorOffset = 0;
@@ -194,11 +194,14 @@ class DOMSelection {
194
194
  return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
195
195
  this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
196
196
  }
197
- set(domSel) {
198
- this.anchorNode = domSel.anchorNode;
199
- this.anchorOffset = domSel.anchorOffset;
200
- this.focusNode = domSel.focusNode;
201
- this.focusOffset = domSel.focusOffset;
197
+ setRange(range) {
198
+ this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
199
+ }
200
+ set(anchorNode, anchorOffset, focusNode, focusOffset) {
201
+ this.anchorNode = anchorNode;
202
+ this.anchorOffset = anchorOffset;
203
+ this.focusNode = focusNode;
204
+ this.focusOffset = focusOffset;
202
205
  }
203
206
  }
204
207
  let preventScrollSupported = null;
@@ -1905,7 +1908,7 @@ class ViewUpdate {
1905
1908
  Whether the document changed in this update.
1906
1909
  */
1907
1910
  get docChanged() {
1908
- return this.transactions.some(tr => tr.docChanged);
1911
+ return !this.changes.empty;
1909
1912
  }
1910
1913
  /**
1911
1914
  Whether the selection was explicitly set in this update.
@@ -1939,9 +1942,10 @@ class DocView extends ContentView {
1939
1942
  // we don't mess it up when reading it back it
1940
1943
  this.impreciseAnchor = null;
1941
1944
  this.impreciseHead = null;
1945
+ this.forceSelection = false;
1942
1946
  // Used by the resize observer to ignore resizes that we caused
1943
1947
  // ourselves
1944
- this.lastUpdate = 0;
1948
+ this.lastUpdate = Date.now();
1945
1949
  this.setDOM(view.contentDOM);
1946
1950
  this.children = [new LineView];
1947
1951
  this.children[0].setParent(this);
@@ -1955,7 +1959,6 @@ class DocView extends ContentView {
1955
1959
  // position, if we know the editor is going to scroll that position
1956
1960
  // into view.
1957
1961
  update(update) {
1958
- this.lastUpdate = Date.now();
1959
1962
  let changedRanges = update.changedRanges;
1960
1963
  if (this.minWidth > 0 && changedRanges.length) {
1961
1964
  if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
@@ -1975,21 +1978,19 @@ class DocView extends ContentView {
1975
1978
  // getSelection than the one that it actually shows to the user.
1976
1979
  // This forces a selection update when lines are joined to work
1977
1980
  // around that. Issue #54
1978
- let forceSelection = (browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1979
- update.state.doc.lines != update.startState.doc.lines;
1981
+ if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1982
+ update.state.doc.lines != update.startState.doc.lines)
1983
+ this.forceSelection = true;
1980
1984
  let prevDeco = this.decorations, deco = this.updateDeco();
1981
1985
  let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1982
1986
  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);
1987
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
1989
1988
  return false;
1990
1989
  }
1991
1990
  else {
1992
- this.updateInner(changedRanges, deco, update.startState.doc.length, forceSelection, pointerSel);
1991
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
1992
+ if (update.transactions.length)
1993
+ this.lastUpdate = Date.now();
1993
1994
  return true;
1994
1995
  }
1995
1996
  }
@@ -1997,13 +1998,16 @@ class DocView extends ContentView {
1997
1998
  if (this.dirty) {
1998
1999
  this.view.observer.ignore(() => this.view.docView.sync());
1999
2000
  this.dirty = 0 /* Not */;
2001
+ this.updateSelection(true);
2000
2002
  }
2001
- if (sel)
2003
+ else {
2002
2004
  this.updateSelection();
2005
+ }
2003
2006
  }
2004
2007
  // Used both by update and checkLayout do perform the actual DOM
2005
2008
  // update
2006
- updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
2009
+ updateInner(changes, deco, oldLength) {
2010
+ this.view.viewState.mustMeasureContent = true;
2007
2011
  this.updateChildren(changes, deco, oldLength);
2008
2012
  let { observer } = this.view;
2009
2013
  observer.ignore(() => {
@@ -2011,7 +2015,7 @@ class DocView extends ContentView {
2011
2015
  // messes with the scroll position during DOM mutation (though
2012
2016
  // no relayout is triggered and I cannot imagine how it can
2013
2017
  // recompute the scroll position without a layout)
2014
- this.dom.style.height = this.view.viewState.domHeight + "px";
2018
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2015
2019
  this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2016
2020
  // Chrome will sometimes, when DOM mutations occur directly
2017
2021
  // around the selection, get confused and report a different
@@ -2021,8 +2025,7 @@ class DocView extends ContentView {
2021
2025
  this.sync(track);
2022
2026
  this.dirty = 0 /* Not */;
2023
2027
  if (track && (track.written || observer.selectionRange.focusNode != track.node))
2024
- forceSelection = true;
2025
- this.updateSelection(forceSelection, pointerSel);
2028
+ this.forceSelection = true;
2026
2029
  this.dom.style.height = "";
2027
2030
  });
2028
2031
  let gaps = [];
@@ -2108,10 +2111,14 @@ class DocView extends ContentView {
2108
2111
  this.replaceChildren(fromI, toI, content);
2109
2112
  }
2110
2113
  // Sync the DOM selection to this.state.selection
2111
- updateSelection(force = false, fromPointer = false) {
2114
+ updateSelection(mustRead = false, fromPointer = false) {
2115
+ if (mustRead)
2116
+ this.view.observer.readSelectionRange();
2112
2117
  if (!(fromPointer || this.mayControlSelection()) ||
2113
2118
  browser.ios && this.view.inputState.rapidCompositionStart)
2114
2119
  return;
2120
+ let force = this.forceSelection;
2121
+ this.forceSelection = false;
2115
2122
  let main = this.view.state.selection.main;
2116
2123
  // FIXME need to handle the case where the selection falls inside a block range
2117
2124
  let anchor = this.domAtPos(main.anchor);
@@ -2293,7 +2300,7 @@ class DocView extends ContentView {
2293
2300
  let next = i == vs.viewports.length ? null : vs.viewports[i];
2294
2301
  let end = next ? next.from - 1 : this.length;
2295
2302
  if (end > pos) {
2296
- let height = vs.lineAt(end, 0).bottom - vs.lineAt(pos, 0).top;
2303
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2297
2304
  deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2298
2305
  }
2299
2306
  if (!next)
@@ -2899,13 +2906,14 @@ function domPosInText(node, x, y) {
2899
2906
  }
2900
2907
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2901
2908
  var _a;
2902
- let content = view.contentDOM.getBoundingClientRect(), block;
2909
+ let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2903
2910
  let halfLine = view.defaultLineHeight / 2;
2911
+ let block, yOffset = y - docTop;
2904
2912
  for (let bounced = false;;) {
2905
- block = view.blockAtHeight(y, content.top);
2906
- if (block.top > y || block.bottom < y) {
2907
- bias = block.top > y ? -1 : 1;
2908
- y = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, y));
2913
+ block = view.elementAtHeight(yOffset);
2914
+ if (block.top > yOffset || block.bottom < yOffset) {
2915
+ bias = block.top > yOffset ? -1 : 1;
2916
+ yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2909
2917
  if (bounced)
2910
2918
  return precise ? null : 0;
2911
2919
  else
@@ -2913,8 +2921,9 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2913
2921
  }
2914
2922
  if (block.type == BlockType.Text)
2915
2923
  break;
2916
- y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2924
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2917
2925
  }
2926
+ y = docTop + yOffset;
2918
2927
  let lineStart = block.from;
2919
2928
  // Clip x to the viewport sides
2920
2929
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
@@ -3027,17 +3036,17 @@ function moveVertically(view, start, forward, distance) {
3027
3036
  return EditorSelection.cursor(startPos);
3028
3037
  let goal = start.goalColumn, startY;
3029
3038
  let rect = view.contentDOM.getBoundingClientRect();
3030
- let startCoords = view.coordsAtPos(startPos);
3039
+ let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
3031
3040
  if (startCoords) {
3032
3041
  if (goal == null)
3033
3042
  goal = startCoords.left - rect.left;
3034
3043
  startY = dir < 0 ? startCoords.top : startCoords.bottom;
3035
3044
  }
3036
3045
  else {
3037
- let line = view.viewState.lineAt(startPos, view.dom.getBoundingClientRect().top);
3046
+ let line = view.viewState.lineBlockAt(startPos - docTop);
3038
3047
  if (goal == null)
3039
3048
  goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3040
- startY = dir < 0 ? line.top : line.bottom;
3049
+ startY = (dir < 0 ? line.top : line.bottom) + docTop;
3041
3050
  }
3042
3051
  let resolvedGoal = rect.left + goal;
3043
3052
  let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
@@ -3288,7 +3297,7 @@ class MouseSelection {
3288
3297
  this.extend = startEvent.shiftKey;
3289
3298
  this.multiple = view.state.facet(EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
3290
3299
  this.dragMove = dragMovesSelection(view, startEvent);
3291
- this.dragging = isInPrimarySelection(view, startEvent) ? null : false;
3300
+ this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
3292
3301
  // When clicking outside of the selection, immediately apply the
3293
3302
  // effect of starting the selection
3294
3303
  if (this.dragging === false) {
@@ -3509,7 +3518,7 @@ function basicMouseSelection(view, event) {
3509
3518
  let last = start, lastEvent = event;
3510
3519
  return {
3511
3520
  update(update) {
3512
- if (update.changes) {
3521
+ if (update.docChanged) {
3513
3522
  if (start)
3514
3523
  start.pos = update.changes.mapPos(start.pos);
3515
3524
  startSel = startSel.map(update.changes);
@@ -3773,7 +3782,10 @@ class HeightOracle {
3773
3782
  return lines * this.lineHeight;
3774
3783
  }
3775
3784
  setDoc(doc) { this.doc = doc; return this; }
3776
- mustRefresh(lineHeights, whiteSpace, direction) {
3785
+ mustRefreshForStyle(whiteSpace, direction) {
3786
+ return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3787
+ }
3788
+ mustRefreshForHeights(lineHeights) {
3777
3789
  let newHeight = false;
3778
3790
  for (let i = 0; i < lineHeights.length; i++) {
3779
3791
  let h = lineHeights[i];
@@ -3785,7 +3797,7 @@ class HeightOracle {
3785
3797
  this.heightSamples[Math.floor(h * 10)] = true;
3786
3798
  }
3787
3799
  }
3788
- return newHeight || (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3800
+ return newHeight;
3789
3801
  }
3790
3802
  refresh(whiteSpace, direction, lineHeight, charWidth, lineLength, knownHeights) {
3791
3803
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
@@ -3839,7 +3851,8 @@ class BlockInfo {
3839
3851
  */
3840
3852
  length,
3841
3853
  /**
3842
- The top position of the element.
3854
+ The top position of the element (relative to the top of the
3855
+ document).
3843
3856
  */
3844
3857
  top,
3845
3858
  /**
@@ -3873,13 +3886,19 @@ class BlockInfo {
3873
3886
  .concat(Array.isArray(other.type) ? other.type : [other]);
3874
3887
  return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, detail);
3875
3888
  }
3889
+ /**
3890
+ FIXME remove on next breaking release @internal
3891
+ */
3892
+ moveY(offset) {
3893
+ 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);
3894
+ }
3876
3895
  }
3877
3896
  var QueryType = /*@__PURE__*/(function (QueryType) {
3878
3897
  QueryType[QueryType["ByPos"] = 0] = "ByPos";
3879
3898
  QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
3880
3899
  QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
3881
3900
  return QueryType})(QueryType || (QueryType = {}));
3882
- const Epsilon = 1e-4;
3901
+ const Epsilon = 1e-3;
3883
3902
  class HeightMap {
3884
3903
  constructor(length, // The number of characters covered
3885
3904
  height, // Height of this part of the document
@@ -4106,22 +4125,30 @@ class HeightMapGap extends HeightMap {
4106
4125
  // can't be widgets or collapsed ranges in those lines, because
4107
4126
  // they would already have been added to the heightmap (gaps
4108
4127
  // only contain plain text).
4109
- let nodes = [], pos = Math.max(offset, measured.from);
4128
+ let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
4129
+ let wasChanged = oracle.heightChanged;
4110
4130
  if (measured.from > offset)
4111
4131
  nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
4112
4132
  while (pos <= end && measured.more) {
4113
4133
  let len = oracle.doc.lineAt(pos).length;
4114
4134
  if (nodes.length)
4115
4135
  nodes.push(null);
4116
- let line = new HeightMapText(len, measured.heights[measured.index++]);
4136
+ let height = measured.heights[measured.index++];
4137
+ if (singleHeight == -1)
4138
+ singleHeight = height;
4139
+ else if (Math.abs(height - singleHeight) >= Epsilon)
4140
+ singleHeight = -2;
4141
+ let line = new HeightMapText(len, height);
4117
4142
  line.outdated = false;
4118
4143
  nodes.push(line);
4119
4144
  pos += len + 1;
4120
4145
  }
4121
4146
  if (pos <= end)
4122
4147
  nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
4123
- oracle.heightChanged = true;
4124
- return HeightMap.of(nodes);
4148
+ let result = HeightMap.of(nodes);
4149
+ oracle.heightChanged = wasChanged || singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
4150
+ Math.abs(singleHeight - this.lines(oracle.doc, offset).lineHeight) >= Epsilon;
4151
+ return result;
4125
4152
  }
4126
4153
  else if (force || this.outdated) {
4127
4154
  this.setHeight(oracle, oracle.heightForGap(offset, offset + this.length));
@@ -4479,7 +4506,8 @@ class ViewState {
4479
4506
  this.inView = true;
4480
4507
  this.paddingTop = 0;
4481
4508
  this.paddingBottom = 0;
4482
- this.contentWidth = 0;
4509
+ this.contentDOMWidth = 0;
4510
+ this.contentDOMHeight = 0;
4483
4511
  this.editorHeight = 0;
4484
4512
  this.heightOracle = new HeightOracle;
4485
4513
  // See VP.MaxDOMHeight
@@ -4487,6 +4515,9 @@ class ViewState {
4487
4515
  this.scrollTarget = null;
4488
4516
  // Briefly set to true when printing, to disable viewport limiting
4489
4517
  this.printing = false;
4518
+ // Flag set when editor content was redrawn, so that the next
4519
+ // measure stage knows it must read DOM layout
4520
+ this.mustMeasureContent = true;
4490
4521
  this.visibleRanges = [];
4491
4522
  // Cursor 'assoc' is only significant when the cursor is on a line
4492
4523
  // wrap point, where it must stick to the character that it is
@@ -4499,6 +4530,7 @@ class ViewState {
4499
4530
  this.mustEnforceCursorAssoc = false;
4500
4531
  this.heightMap = HeightMap.empty().applyChanges(state.facet(decorations), Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
4501
4532
  this.viewport = this.getViewport(0, null);
4533
+ this.updateViewportLines();
4502
4534
  this.updateForViewport();
4503
4535
  this.lineGaps = this.ensureLineGaps([]);
4504
4536
  this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(false)));
@@ -4509,7 +4541,7 @@ class ViewState {
4509
4541
  for (let i = 0; i <= 1; i++) {
4510
4542
  let pos = i ? main.head : main.anchor;
4511
4543
  if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
4512
- let { from, to } = this.lineAt(pos, 0);
4544
+ let { from, to } = this.lineBlockAt(pos);
4513
4545
  viewports.push(new Viewport(from, to));
4514
4546
  }
4515
4547
  }
@@ -4517,6 +4549,12 @@ class ViewState {
4517
4549
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4518
4550
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4519
4551
  }
4552
+ updateViewportLines() {
4553
+ this.viewportLines = [];
4554
+ this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, block => {
4555
+ this.viewportLines.push(this.scaler.scale == 1 ? block : scaleBlock(block, this.scaler));
4556
+ });
4557
+ }
4520
4558
  update(update, scrollTarget = null) {
4521
4559
  let prev = this.state;
4522
4560
  this.state = update.state;
@@ -4531,7 +4569,11 @@ class ViewState {
4531
4569
  if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4532
4570
  !this.viewportIsAppropriate(viewport))
4533
4571
  viewport = this.getViewport(0, scrollTarget);
4572
+ let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4573
+ viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4534
4574
  this.viewport = viewport;
4575
+ if (updateLines)
4576
+ this.updateViewportLines();
4535
4577
  this.updateForViewport();
4536
4578
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4537
4579
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
@@ -4542,13 +4584,17 @@ class ViewState {
4542
4584
  update.state.selection.main.empty && update.state.selection.main.assoc)
4543
4585
  this.mustEnforceCursorAssoc = true;
4544
4586
  }
4545
- measure(docView, repeated) {
4546
- let dom = docView.dom, whiteSpace = "", direction = Direction.LTR;
4547
- let result = 0;
4548
- if (!repeated) {
4587
+ measure(view) {
4588
+ let dom = view.contentDOM, style = window.getComputedStyle(dom);
4589
+ let oracle = this.heightOracle;
4590
+ let whiteSpace = style.whiteSpace, direction = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
4591
+ let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4592
+ let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4593
+ let result = 0, bias = 0;
4594
+ if (measureContent) {
4595
+ this.mustMeasureContent = false;
4596
+ this.contentDOMHeight = dom.clientHeight;
4549
4597
  // Vertical padding
4550
- let style = window.getComputedStyle(dom);
4551
- whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? Direction.RTL : Direction.LTR);
4552
4598
  let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4553
4599
  if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4554
4600
  result |= 8 /* Geometry */;
@@ -4563,39 +4609,42 @@ class ViewState {
4563
4609
  this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4564
4610
  if (!this.inView)
4565
4611
  return 0;
4566
- let lineHeights = docView.measureVisibleLineHeights();
4567
- let refresh = false, bias = 0, oracle = this.heightOracle;
4568
- if (!repeated) {
4569
- let contentWidth = docView.dom.clientWidth;
4570
- if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
4571
- oracle.lineWrapping && Math.abs(contentWidth - this.contentWidth) > oracle.charWidth) {
4572
- let { lineHeight, charWidth } = docView.measureTextSize();
4612
+ if (measureContent) {
4613
+ let lineHeights = view.docView.measureVisibleLineHeights();
4614
+ if (oracle.mustRefreshForHeights(lineHeights))
4615
+ refresh = true;
4616
+ let contentWidth = dom.clientWidth;
4617
+ if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4618
+ let { lineHeight, charWidth } = view.docView.measureTextSize();
4573
4619
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4574
4620
  if (refresh) {
4575
- docView.minWidth = 0;
4621
+ view.docView.minWidth = 0;
4576
4622
  result |= 8 /* Geometry */;
4577
4623
  }
4578
4624
  }
4579
- if (this.contentWidth != contentWidth) {
4580
- this.contentWidth = contentWidth;
4625
+ if (this.contentDOMWidth != contentWidth) {
4626
+ this.contentDOMWidth = contentWidth;
4581
4627
  result |= 8 /* Geometry */;
4582
4628
  }
4583
- if (this.editorHeight != docView.view.scrollDOM.clientHeight) {
4584
- this.editorHeight = docView.view.scrollDOM.clientHeight;
4629
+ if (this.editorHeight != view.scrollDOM.clientHeight) {
4630
+ this.editorHeight = view.scrollDOM.clientHeight;
4585
4631
  result |= 8 /* Geometry */;
4586
4632
  }
4587
4633
  if (dTop > 0 && dBottom > 0)
4588
4634
  bias = Math.max(dTop, dBottom);
4589
4635
  else if (dTop < 0 && dBottom < 0)
4590
4636
  bias = Math.min(dTop, dBottom);
4591
- }
4592
- oracle.heightChanged = false;
4593
- this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4594
- if (oracle.heightChanged)
4595
- result |= 2 /* Height */;
4596
- if (!this.viewportIsAppropriate(this.viewport, bias) ||
4597
- this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4637
+ oracle.heightChanged = false;
4638
+ this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4639
+ if (oracle.heightChanged)
4640
+ result |= 2 /* Height */;
4641
+ }
4642
+ let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
4643
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4644
+ if (viewportChange)
4598
4645
  this.viewport = this.getViewport(bias, this.scrollTarget);
4646
+ if ((result & 2 /* Height */) || viewportChange)
4647
+ this.updateViewportLines();
4599
4648
  this.updateForViewport();
4600
4649
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4601
4650
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
@@ -4606,12 +4655,12 @@ class ViewState {
4606
4655
  // to a line end is going to trigger a layout anyway, so it
4607
4656
  // can't be a pure write. It should be rare that it does any
4608
4657
  // writing.
4609
- docView.enforceCursorAssoc();
4658
+ view.docView.enforceCursorAssoc();
4610
4659
  }
4611
4660
  return result;
4612
4661
  }
4613
- get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4614
- get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4662
+ get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
4663
+ get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
4615
4664
  getViewport(bias, scrollTarget) {
4616
4665
  // This will divide VP.Margin between the top and the
4617
4666
  // bottom, depending on the bias (the change in viewport position
@@ -4671,12 +4720,12 @@ class ViewState {
4671
4720
  // This won't work at all in predominantly right-to-left text.
4672
4721
  if (this.heightOracle.direction != Direction.LTR)
4673
4722
  return gaps;
4674
- this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4723
+ for (let line of this.viewportLines) {
4675
4724
  if (line.length < 4000 /* DoubleMargin */)
4676
- return;
4725
+ continue;
4677
4726
  let structure = lineStructure(line.from, line.to, this.state);
4678
4727
  if (structure.total < 4000 /* DoubleMargin */)
4679
- return;
4728
+ continue;
4680
4729
  let viewFrom, viewTo;
4681
4730
  if (this.heightOracle.lineWrapping) {
4682
4731
  let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
@@ -4706,7 +4755,7 @@ class ViewState {
4706
4755
  Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4707
4756
  new LineGap(from, to, this.gapSize(line, from, to, structure)));
4708
4757
  }
4709
- });
4758
+ }
4710
4759
  return gaps;
4711
4760
  }
4712
4761
  gapSize(line, from, to, structure) {
@@ -4738,27 +4787,18 @@ class ViewState {
4738
4787
  this.visibleRanges = ranges;
4739
4788
  return changed ? 4 /* Viewport */ : 0;
4740
4789
  }
4741
- lineAt(pos, editorTop) {
4742
- editorTop += this.paddingTop;
4743
- return scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, editorTop, 0), this.scaler, editorTop);
4790
+ lineBlockAt(pos) {
4791
+ return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
4792
+ scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, 0, 0), this.scaler);
4744
4793
  }
4745
- lineAtHeight(height, editorTop) {
4746
- editorTop += this.paddingTop;
4747
- return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height, editorTop), QueryType.ByHeight, this.state.doc, editorTop, 0), this.scaler, editorTop);
4794
+ lineBlockAtHeight(height) {
4795
+ return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.state.doc, 0, 0), this.scaler);
4748
4796
  }
4749
- blockAtHeight(height, editorTop) {
4750
- editorTop += this.paddingTop;
4751
- return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height, editorTop), this.state.doc, editorTop, 0), this.scaler, editorTop);
4752
- }
4753
- forEachLine(from, to, f, editorTop) {
4754
- editorTop += this.paddingTop;
4755
- return this.heightMap.forEachLine(from, to, this.state.doc, editorTop, 0, this.scaler.scale == 1 ? f : b => f(scaleBlock(b, this.scaler, editorTop)));
4797
+ elementAtHeight(height) {
4798
+ return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4756
4799
  }
4757
4800
  get contentHeight() {
4758
- return this.domHeight + this.paddingTop + this.paddingBottom;
4759
- }
4760
- get domHeight() {
4761
- return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4801
+ return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4762
4802
  }
4763
4803
  }
4764
4804
  class Viewport {
@@ -4855,36 +4895,34 @@ class BigScaler {
4855
4895
  base = obj.bottom;
4856
4896
  }
4857
4897
  }
4858
- toDOM(n, top) {
4859
- n -= top;
4898
+ toDOM(n) {
4860
4899
  for (let i = 0, base = 0, domBase = 0;; i++) {
4861
4900
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4862
4901
  if (!vp || n < vp.top)
4863
- return domBase + (n - base) * this.scale + top;
4902
+ return domBase + (n - base) * this.scale;
4864
4903
  if (n <= vp.bottom)
4865
- return vp.domTop + (n - vp.top) + top;
4904
+ return vp.domTop + (n - vp.top);
4866
4905
  base = vp.bottom;
4867
4906
  domBase = vp.domBottom;
4868
4907
  }
4869
4908
  }
4870
- fromDOM(n, top) {
4871
- n -= top;
4909
+ fromDOM(n) {
4872
4910
  for (let i = 0, base = 0, domBase = 0;; i++) {
4873
4911
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4874
4912
  if (!vp || n < vp.domTop)
4875
- return base + (n - domBase) / this.scale + top;
4913
+ return base + (n - domBase) / this.scale;
4876
4914
  if (n <= vp.domBottom)
4877
- return vp.top + (n - vp.domTop) + top;
4915
+ return vp.top + (n - vp.domTop);
4878
4916
  base = vp.bottom;
4879
4917
  domBase = vp.domBottom;
4880
4918
  }
4881
4919
  }
4882
4920
  }
4883
- function scaleBlock(block, scaler, top) {
4921
+ function scaleBlock(block, scaler) {
4884
4922
  if (scaler.scale == 1)
4885
4923
  return block;
4886
- let bTop = scaler.toDOM(block.top, top), bBottom = scaler.toDOM(block.bottom, top);
4887
- return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler, top)) : block.type);
4924
+ let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
4925
+ return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler)) : block.type);
4888
4926
  }
4889
4927
 
4890
4928
  const theme = /*@__PURE__*/Facet.define({ combine: strs => strs.join(" ") });
@@ -5069,25 +5107,30 @@ class DOMObserver {
5069
5107
  this.onChange = onChange;
5070
5108
  this.onScrollChanged = onScrollChanged;
5071
5109
  this.active = false;
5072
- this.ignoreSelection = new DOMSelection;
5110
+ // The known selection. Kept in our own object, as opposed to just
5111
+ // directly accessing the selection because:
5112
+ // - Safari doesn't report the right selection in shadow DOM
5113
+ // - Reading from the selection forces a DOM layout
5114
+ // - This way, we can ignore selectionchange events if we have
5115
+ // already seen the 'new' selection
5116
+ this.selectionRange = new DOMSelectionState;
5117
+ // Set when a selection change is detected, cleared on flush
5118
+ this.selectionChanged = false;
5073
5119
  this.delayedFlush = -1;
5120
+ this.resizeTimeout = -1;
5074
5121
  this.queue = [];
5075
- this.lastFlush = 0;
5076
5122
  this.scrollTargets = [];
5077
5123
  this.intersection = null;
5078
5124
  this.resize = null;
5079
5125
  this.intersecting = false;
5080
5126
  this.gapIntersection = null;
5081
5127
  this.gaps = [];
5082
- // Used to work around a Safari Selection/shadow DOM bug (#414)
5083
- this._selectionRange = null;
5084
5128
  // Timeout for scheduling check of the parents that need scroll handlers
5085
5129
  this.parentCheck = -1;
5086
5130
  this.dom = view.contentDOM;
5087
5131
  this.observer = new MutationObserver(mutations => {
5088
5132
  for (let mut of mutations)
5089
5133
  this.queue.push(mut);
5090
- this._selectionRange = null;
5091
5134
  // IE11 will sometimes (on typing over a selection or
5092
5135
  // backspacing out a single character text node) call the
5093
5136
  // observer callback before actually updating the DOM.
@@ -5114,8 +5157,11 @@ class DOMObserver {
5114
5157
  this.onSelectionChange = this.onSelectionChange.bind(this);
5115
5158
  if (typeof ResizeObserver == "function") {
5116
5159
  this.resize = new ResizeObserver(() => {
5117
- if (this.view.docView.lastUpdate < Date.now() - 100)
5118
- this.view.requestMeasure();
5160
+ if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
5161
+ this.resizeTimeout = setTimeout(() => {
5162
+ this.resizeTimeout = -1;
5163
+ this.view.requestMeasure();
5164
+ }, 50);
5119
5165
  });
5120
5166
  this.resize.observe(view.scrollDOM);
5121
5167
  }
@@ -5139,10 +5185,12 @@ class DOMObserver {
5139
5185
  }, {});
5140
5186
  }
5141
5187
  this.listenForScroll();
5188
+ this.readSelectionRange();
5189
+ this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5142
5190
  }
5143
5191
  onScroll(e) {
5144
5192
  if (this.intersecting)
5145
- this.flush();
5193
+ this.flush(false);
5146
5194
  this.onScrollChanged(e);
5147
5195
  }
5148
5196
  updateGaps(gaps) {
@@ -5154,8 +5202,8 @@ class DOMObserver {
5154
5202
  }
5155
5203
  }
5156
5204
  onSelectionChange(event) {
5157
- if (this.lastFlush < Date.now() - 50)
5158
- this._selectionRange = null;
5205
+ if (!this.readSelectionRange())
5206
+ return;
5159
5207
  let { view } = this, sel = this.selectionRange;
5160
5208
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
5161
5209
  return;
@@ -5170,24 +5218,22 @@ class DOMObserver {
5170
5218
  sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
5171
5219
  this.flushSoon();
5172
5220
  else
5173
- this.flush();
5174
- }
5175
- get selectionRange() {
5176
- if (!this._selectionRange) {
5177
- let { root } = this.view, sel = getSelection(root);
5178
- // The Selection object is broken in shadow roots in Safari. See
5179
- // https://github.com/codemirror/codemirror.next/issues/414
5180
- if (browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM)
5181
- sel = safariSelectionRangeHack(this.view) || sel;
5182
- this._selectionRange = sel;
5183
- }
5184
- return this._selectionRange;
5221
+ this.flush(false);
5222
+ }
5223
+ readSelectionRange() {
5224
+ let { root } = this.view, domSel = getSelection(root);
5225
+ // The Selection object is broken in shadow roots in Safari. See
5226
+ // https://github.com/codemirror/codemirror.next/issues/414
5227
+ let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
5228
+ safariSelectionRangeHack(this.view) || domSel;
5229
+ if (this.selectionRange.eq(range))
5230
+ return false;
5231
+ this.selectionRange.setRange(range);
5232
+ return this.selectionChanged = true;
5185
5233
  }
5186
5234
  setSelectionRange(anchor, head) {
5187
- var _a;
5188
- if (!((_a = this._selectionRange) === null || _a === void 0 ? void 0 : _a.type))
5189
- this._selectionRange = { anchorNode: anchor.node, anchorOffset: anchor.offset,
5190
- focusNode: head.node, focusOffset: head.offset };
5235
+ this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
5236
+ this.selectionChanged = false;
5191
5237
  }
5192
5238
  listenForScroll() {
5193
5239
  this.parentCheck = -1;
@@ -5234,7 +5280,6 @@ class DOMObserver {
5234
5280
  if (this.active)
5235
5281
  return;
5236
5282
  this.observer.observe(this.dom, observeOptions);
5237
- this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5238
5283
  if (useCharData)
5239
5284
  this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
5240
5285
  this.active = true;
@@ -5244,18 +5289,14 @@ class DOMObserver {
5244
5289
  return;
5245
5290
  this.active = false;
5246
5291
  this.observer.disconnect();
5247
- this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5248
5292
  if (useCharData)
5249
5293
  this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
5250
5294
  }
5251
- clearSelection() {
5252
- this.ignoreSelection.set(this.selectionRange);
5253
- }
5254
5295
  // Throw away any pending changes
5255
5296
  clear() {
5256
5297
  this.observer.takeRecords();
5257
5298
  this.queue.length = 0;
5258
- this.clearSelection();
5299
+ this.selectionChanged = false;
5259
5300
  }
5260
5301
  flushSoon() {
5261
5302
  if (this.delayedFlush < 0)
@@ -5292,24 +5333,24 @@ class DOMObserver {
5292
5333
  return { from, to, typeOver };
5293
5334
  }
5294
5335
  // Apply pending changes, if any
5295
- flush() {
5336
+ flush(readSelection = true) {
5337
+ if (readSelection)
5338
+ this.readSelectionRange();
5296
5339
  // Completely hold off flushing when pending keys are set—the code
5297
5340
  // managing those will make sure processRecords is called and the
5298
5341
  // view is resynchronized after
5299
5342
  if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5300
5343
  return;
5301
- this.lastFlush = Date.now();
5302
5344
  let { from, to, typeOver } = this.processRecords();
5303
- let selection = this.selectionRange;
5304
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5345
+ let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5305
5346
  if (from < 0 && !newSel)
5306
5347
  return;
5348
+ this.selectionChanged = false;
5307
5349
  let startState = this.view.state;
5308
5350
  this.onChange(from, to, typeOver);
5309
5351
  // The view wasn't updated
5310
5352
  if (this.view.state == startState)
5311
5353
  this.view.docView.reset(newSel);
5312
- this.clearSelection();
5313
5354
  }
5314
5355
  readMutation(rec) {
5315
5356
  let cView = this.view.docView.nearest(rec.target);
@@ -5341,6 +5382,7 @@ class DOMObserver {
5341
5382
  dom.removeEventListener("scroll", this.onScroll);
5342
5383
  window.removeEventListener("scroll", this.onScroll);
5343
5384
  clearTimeout(this.parentCheck);
5385
+ clearTimeout(this.resizeTimeout);
5344
5386
  }
5345
5387
  }
5346
5388
  function findChild(cView, dom, dir) {
@@ -5353,6 +5395,7 @@ function findChild(cView, dom, dir) {
5353
5395
  }
5354
5396
  return null;
5355
5397
  }
5398
+ // Used to work around a Safari Selection/shadow DOM bug (#414)
5356
5399
  function safariSelectionRangeHack(view) {
5357
5400
  let found = null;
5358
5401
  // Because Safari (at least in 2018-2021) doesn't provide regular
@@ -5772,6 +5815,7 @@ class EditorView {
5772
5815
  this.mountStyles();
5773
5816
  this.updateAttrs();
5774
5817
  this.showAnnouncements(transactions);
5818
+ this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
5775
5819
  }
5776
5820
  finally {
5777
5821
  this.updateState = 0 /* Idle */;
@@ -5857,11 +5901,11 @@ class EditorView {
5857
5901
  for (let i = 0;; i++) {
5858
5902
  this.updateState = 1 /* Measuring */;
5859
5903
  let oldViewport = this.viewport;
5860
- let changed = this.viewState.measure(this.docView, i > 0);
5904
+ let changed = this.viewState.measure(this);
5861
5905
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5862
5906
  break;
5863
5907
  if (i > 5) {
5864
- console.warn("Viewport failed to stabilize");
5908
+ console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5865
5909
  break;
5866
5910
  }
5867
5911
  let measuring = [];
@@ -5877,7 +5921,7 @@ class EditorView {
5877
5921
  return BadMeasure;
5878
5922
  }
5879
5923
  });
5880
- let update = new ViewUpdate(this, this.state);
5924
+ let update = new ViewUpdate(this, this.state), redrawn = false;
5881
5925
  update.flags |= changed;
5882
5926
  if (!updated)
5883
5927
  updated = update;
@@ -5887,14 +5931,15 @@ class EditorView {
5887
5931
  if (!update.empty) {
5888
5932
  this.updatePlugins(update);
5889
5933
  this.inputState.update(update);
5934
+ this.updateAttrs();
5935
+ redrawn = this.docView.update(update);
5890
5936
  }
5891
- this.updateAttrs();
5892
- if (changed)
5893
- this.docView.update(update);
5894
5937
  for (let i = 0; i < measuring.length; i++)
5895
5938
  if (measured[i] != BadMeasure) {
5896
5939
  try {
5897
- measuring[i].write(measured[i], this);
5940
+ let m = measuring[i];
5941
+ if (m.write)
5942
+ m.write(measured[i], this);
5898
5943
  }
5899
5944
  catch (e) {
5900
5945
  logException(this.state, e);
@@ -5904,6 +5949,8 @@ class EditorView {
5904
5949
  this.docView.scrollIntoView(this.viewState.scrollTarget);
5905
5950
  this.viewState.scrollTarget = null;
5906
5951
  }
5952
+ if (redrawn)
5953
+ this.docView.updateSelection(true);
5907
5954
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5908
5955
  break;
5909
5956
  }
@@ -5928,8 +5975,6 @@ class EditorView {
5928
5975
  let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
5929
5976
  class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
5930
5977
  });
5931
- updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5932
- this.editorAttrs = editorAttrs;
5933
5978
  let contentAttrs = {
5934
5979
  spellcheck: "false",
5935
5980
  autocorrect: "off",
@@ -5944,7 +5989,11 @@ class EditorView {
5944
5989
  if (this.state.readOnly)
5945
5990
  contentAttrs["aria-readonly"] = "true";
5946
5991
  combineAttrs(this.state.facet(contentAttributes), contentAttrs);
5947
- updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5992
+ this.observer.ignore(() => {
5993
+ updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5994
+ updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5995
+ });
5996
+ this.editorAttrs = editorAttrs;
5948
5997
  this.contentAttrs = contentAttrs;
5949
5998
  }
5950
5999
  showAnnouncements(trs) {
@@ -6014,6 +6063,20 @@ class EditorView {
6014
6063
  return null;
6015
6064
  }
6016
6065
  /**
6066
+ The top position of the document, in screen coordinates. This
6067
+ may be negative when the editor is scrolled down. Points
6068
+ directly to the top of the first line, not above the padding.
6069
+ */
6070
+ get documentTop() {
6071
+ return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
6072
+ }
6073
+ /**
6074
+ Reports the padding above and below the document.
6075
+ */
6076
+ get documentPadding() {
6077
+ return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
6078
+ }
6079
+ /**
6017
6080
  Find the line or block widget at the given vertical position.
6018
6081
 
6019
6082
  By default, this position is interpreted as a screen position,
@@ -6023,10 +6086,21 @@ class EditorView {
6023
6086
  position, or a precomputed document top
6024
6087
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6025
6088
  queries.
6089
+
6090
+ *Deprecated: use `blockAtHeight` instead.*
6026
6091
  */
6027
6092
  blockAtHeight(height, docTop) {
6093
+ let top = ensureTop(docTop, this);
6094
+ return this.elementAtHeight(height - top).moveY(top);
6095
+ }
6096
+ /**
6097
+ Find the text line or block widget at the given vertical
6098
+ position (which is interpreted as relative to the [top of the
6099
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)
6100
+ */
6101
+ elementAtHeight(height) {
6028
6102
  this.readMeasured();
6029
- return this.viewState.blockAtHeight(height, ensureTop(docTop, this.contentDOM));
6103
+ return this.viewState.elementAtHeight(height);
6030
6104
  }
6031
6105
  /**
6032
6106
  Find information for the visual line (see
@@ -6038,20 +6112,43 @@ class EditorView {
6038
6112
  Defaults to treating `height` as a screen position. See
6039
6113
  [`blockAtHeight`](https://codemirror.net/6/docs/ref/#view.EditorView.blockAtHeight) for the
6040
6114
  interpretation of the `docTop` parameter.
6115
+
6116
+ *Deprecated: use `lineBlockAtHeight` instead.*
6041
6117
  */
6042
6118
  visualLineAtHeight(height, docTop) {
6119
+ let top = ensureTop(docTop, this);
6120
+ return this.lineBlockAtHeight(height - top).moveY(top);
6121
+ }
6122
+ /**
6123
+ Find the line block (see
6124
+ [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
6125
+ height.
6126
+ */
6127
+ lineBlockAtHeight(height) {
6043
6128
  this.readMeasured();
6044
- return this.viewState.lineAtHeight(height, ensureTop(docTop, this.contentDOM));
6129
+ return this.viewState.lineBlockAtHeight(height);
6045
6130
  }
6046
6131
  /**
6047
6132
  Iterate over the height information of the visual lines in the
6048
6133
  viewport. The heights of lines are reported relative to the
6049
6134
  given document top, which defaults to the screen position of the
6050
6135
  document (forcing a layout).
6136
+
6137
+ *Deprecated: use `viewportLineBlocks` instead.*
6051
6138
  */
6052
6139
  viewportLines(f, docTop) {
6053
- let { from, to } = this.viewport;
6054
- this.viewState.forEachLine(from, to, f, ensureTop(docTop, this.contentDOM));
6140
+ let top = ensureTop(docTop, this);
6141
+ for (let line of this.viewportLineBlocks)
6142
+ f(line.moveY(top));
6143
+ }
6144
+ /**
6145
+ Get the extent and vertical position of all [line
6146
+ blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
6147
+ are relative to the [top of the
6148
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
6149
+ */
6150
+ get viewportLineBlocks() {
6151
+ return this.viewState.viewportLines;
6055
6152
  }
6056
6153
  /**
6057
6154
  Find the extent and height of the visual line (a range delimited
@@ -6062,9 +6159,22 @@ class EditorView {
6062
6159
  argument, which defaults to 0 for this method. You can pass
6063
6160
  `view.contentDOM.getBoundingClientRect().top` here to get screen
6064
6161
  coordinates.
6162
+
6163
+ *Deprecated: use `lineBlockAt` instead.*
6065
6164
  */
6066
6165
  visualLineAt(pos, docTop = 0) {
6067
- return this.viewState.lineAt(pos, docTop);
6166
+ return this.lineBlockAt(pos).moveY(docTop + this.viewState.paddingTop);
6167
+ }
6168
+ /**
6169
+ Find the line block around the given document position. A line
6170
+ block is a range delimited on both sides by either a
6171
+ non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range) line breaks, or the
6172
+ start/end of the document. It will usually just hold a line of
6173
+ text, but may be broken into multiple textblocks by block
6174
+ widgets.
6175
+ */
6176
+ lineBlockAt(pos) {
6177
+ return this.viewState.lineBlockAt(pos);
6068
6178
  }
6069
6179
  /**
6070
6180
  The editor's total content height.
@@ -6397,8 +6507,9 @@ search match).
6397
6507
  EditorView.announce = /*@__PURE__*/StateEffect.define();
6398
6508
  // Maximum line length for which we compute accurate bidi info
6399
6509
  const MaxBidiLine = 4096;
6400
- function ensureTop(given, dom) {
6401
- return given == null ? dom.getBoundingClientRect().top : given;
6510
+ // FIXME remove this and its callers on next breaking release
6511
+ function ensureTop(given, view) {
6512
+ return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6402
6513
  }
6403
6514
  let resizeDebounce = -1;
6404
6515
  function ensureGlobalHandler() {
@@ -6749,7 +6860,7 @@ function wrappedLine(view, pos, inside) {
6749
6860
  type: BlockType.Text };
6750
6861
  }
6751
6862
  function blockAt(view, pos) {
6752
- let line = view.visualLineAt(pos);
6863
+ let line = view.lineBlockAt(pos);
6753
6864
  if (Array.isArray(line.type))
6754
6865
  for (let l of line.type) {
6755
6866
  if (l.to > pos || l.to == pos && (l.to == line.to || l.type == BlockType.Text))
@@ -7140,7 +7251,7 @@ const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
7140
7251
  for (let r of view.state.selection.ranges) {
7141
7252
  if (!r.empty)
7142
7253
  return Decoration.none;
7143
- let line = view.visualLineAt(r.head);
7254
+ let line = view.lineBlockAt(r.head);
7144
7255
  if (line.from > lastLineStart) {
7145
7256
  deco.push(lineDeco.range(line.from));
7146
7257
  lastLineStart = line.from;