@codemirror/view 0.19.21 → 0.19.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.19.22 (2021-11-30)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where editors with large vertical padding (for example via `scrollPastEnd`) could sometimes lose their scroll position on Chrome.
6
+
7
+ Avoid some unnecessary DOM measuring work by more carefully checking whether it is needed.
8
+
9
+ ### New features
10
+
11
+ The new `elementAtHeight`, `lineBlockAtHeight`, and `lineBlockAt` methods provide a simpler and more efficient replacement for the (now deprecated) `blockAtHeight`, `visualLineAtHeight`, and `visualLineAt` methods.
12
+
13
+ The editor view now exports a `documentTop` getter that gives you the vertical position of the top of the document. All height info is queried and reported relative to this top.
14
+
15
+ The editor view's new `viewportLineBlocks` property provides an array of in-viewport line blocks, and replaces the (now deprecated) `viewportLines` method.
16
+
1
17
  ## 0.19.21 (2021-11-26)
2
18
 
3
19
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -1988,10 +1988,7 @@ class DocView extends ContentView {
1988
1988
  let prevDeco = this.decorations, deco = this.updateDeco();
1989
1989
  let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1990
1990
  changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1991
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
1992
- !(update.flags & 4 /* Viewport */) &&
1993
- update.state.selection.main.from >= this.view.viewport.from &&
1994
- update.state.selection.main.to <= this.view.viewport.to) {
1991
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
1995
1992
  return false;
1996
1993
  }
1997
1994
  else {
@@ -2014,6 +2011,7 @@ class DocView extends ContentView {
2014
2011
  // Used both by update and checkLayout do perform the actual DOM
2015
2012
  // update
2016
2013
  updateInner(changes, deco, oldLength) {
2014
+ this.view.viewState.mustMeasureContent = true;
2017
2015
  this.updateChildren(changes, deco, oldLength);
2018
2016
  let { observer } = this.view;
2019
2017
  observer.ignore(() => {
@@ -2021,7 +2019,7 @@ class DocView extends ContentView {
2021
2019
  // messes with the scroll position during DOM mutation (though
2022
2020
  // no relayout is triggered and I cannot imagine how it can
2023
2021
  // recompute the scroll position without a layout)
2024
- this.dom.style.height = this.view.viewState.domHeight + "px";
2022
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2025
2023
  this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2026
2024
  // Chrome will sometimes, when DOM mutations occur directly
2027
2025
  // around the selection, get confused and report a different
@@ -2306,7 +2304,7 @@ class DocView extends ContentView {
2306
2304
  let next = i == vs.viewports.length ? null : vs.viewports[i];
2307
2305
  let end = next ? next.from - 1 : this.length;
2308
2306
  if (end > pos) {
2309
- let height = vs.lineAt(end, 0).bottom - vs.lineAt(pos, 0).top;
2307
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2310
2308
  deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2311
2309
  }
2312
2310
  if (!next)
@@ -2913,13 +2911,14 @@ function domPosInText(node, x, y) {
2913
2911
  }
2914
2912
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2915
2913
  var _a;
2916
- let content = view.contentDOM.getBoundingClientRect(), block;
2914
+ let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2917
2915
  let halfLine = view.defaultLineHeight / 2;
2916
+ let block, yOffset = y - docTop;
2918
2917
  for (let bounced = false;;) {
2919
- block = view.blockAtHeight(y, content.top);
2920
- if (block.top > y || block.bottom < y) {
2921
- bias = block.top > y ? -1 : 1;
2922
- 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));
2923
2922
  if (bounced)
2924
2923
  return precise ? null : 0;
2925
2924
  else
@@ -2927,8 +2926,9 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2927
2926
  }
2928
2927
  if (block.type == exports.BlockType.Text)
2929
2928
  break;
2930
- y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2929
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2931
2930
  }
2931
+ y = docTop + yOffset;
2932
2932
  let lineStart = block.from;
2933
2933
  // Clip x to the viewport sides
2934
2934
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
@@ -3041,17 +3041,17 @@ function moveVertically(view, start, forward, distance) {
3041
3041
  return state.EditorSelection.cursor(startPos);
3042
3042
  let goal = start.goalColumn, startY;
3043
3043
  let rect = view.contentDOM.getBoundingClientRect();
3044
- let startCoords = view.coordsAtPos(startPos);
3044
+ let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
3045
3045
  if (startCoords) {
3046
3046
  if (goal == null)
3047
3047
  goal = startCoords.left - rect.left;
3048
3048
  startY = dir < 0 ? startCoords.top : startCoords.bottom;
3049
3049
  }
3050
3050
  else {
3051
- let line = view.viewState.lineAt(startPos, view.dom.getBoundingClientRect().top);
3051
+ let line = view.viewState.lineBlockAt(startPos - docTop);
3052
3052
  if (goal == null)
3053
3053
  goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3054
- startY = dir < 0 ? line.top : line.bottom;
3054
+ startY = (dir < 0 ? line.top : line.bottom) + docTop;
3055
3055
  }
3056
3056
  let resolvedGoal = rect.left + goal;
3057
3057
  let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
@@ -3302,7 +3302,7 @@ class MouseSelection {
3302
3302
  this.extend = startEvent.shiftKey;
3303
3303
  this.multiple = view.state.facet(state.EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
3304
3304
  this.dragMove = dragMovesSelection(view, startEvent);
3305
- this.dragging = isInPrimarySelection(view, startEvent) ? null : false;
3305
+ this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
3306
3306
  // When clicking outside of the selection, immediately apply the
3307
3307
  // effect of starting the selection
3308
3308
  if (this.dragging === false) {
@@ -3523,7 +3523,7 @@ function basicMouseSelection(view, event) {
3523
3523
  let last = start, lastEvent = event;
3524
3524
  return {
3525
3525
  update(update) {
3526
- if (update.changes) {
3526
+ if (update.docChanged) {
3527
3527
  if (start)
3528
3528
  start.pos = update.changes.mapPos(start.pos);
3529
3529
  startSel = startSel.map(update.changes);
@@ -3787,7 +3787,10 @@ class HeightOracle {
3787
3787
  return lines * this.lineHeight;
3788
3788
  }
3789
3789
  setDoc(doc) { this.doc = doc; return this; }
3790
- mustRefresh(lineHeights, whiteSpace, direction) {
3790
+ mustRefreshForStyle(whiteSpace, direction) {
3791
+ return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3792
+ }
3793
+ mustRefreshForHeights(lineHeights) {
3791
3794
  let newHeight = false;
3792
3795
  for (let i = 0; i < lineHeights.length; i++) {
3793
3796
  let h = lineHeights[i];
@@ -3799,7 +3802,7 @@ class HeightOracle {
3799
3802
  this.heightSamples[Math.floor(h * 10)] = true;
3800
3803
  }
3801
3804
  }
3802
- return newHeight || (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3805
+ return newHeight;
3803
3806
  }
3804
3807
  refresh(whiteSpace, direction, lineHeight, charWidth, lineLength, knownHeights) {
3805
3808
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
@@ -3853,7 +3856,8 @@ class BlockInfo {
3853
3856
  */
3854
3857
  length,
3855
3858
  /**
3856
- The top position of the element.
3859
+ The top position of the element (relative to the top of the
3860
+ document).
3857
3861
  */
3858
3862
  top,
3859
3863
  /**
@@ -3887,6 +3891,12 @@ class BlockInfo {
3887
3891
  .concat(Array.isArray(other.type) ? other.type : [other]);
3888
3892
  return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, detail);
3889
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
+ }
3890
3900
  }
3891
3901
  var QueryType;
3892
3902
  (function (QueryType) {
@@ -3894,7 +3904,7 @@ var QueryType;
3894
3904
  QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
3895
3905
  QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
3896
3906
  })(QueryType || (QueryType = {}));
3897
- const Epsilon = 1e-4;
3907
+ const Epsilon = 1e-3;
3898
3908
  class HeightMap {
3899
3909
  constructor(length, // The number of characters covered
3900
3910
  height, // Height of this part of the document
@@ -4121,22 +4131,30 @@ class HeightMapGap extends HeightMap {
4121
4131
  // can't be widgets or collapsed ranges in those lines, because
4122
4132
  // they would already have been added to the heightmap (gaps
4123
4133
  // only contain plain text).
4124
- let nodes = [], pos = Math.max(offset, measured.from);
4134
+ let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
4135
+ let wasChanged = oracle.heightChanged;
4125
4136
  if (measured.from > offset)
4126
4137
  nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
4127
4138
  while (pos <= end && measured.more) {
4128
4139
  let len = oracle.doc.lineAt(pos).length;
4129
4140
  if (nodes.length)
4130
4141
  nodes.push(null);
4131
- 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);
4132
4148
  line.outdated = false;
4133
4149
  nodes.push(line);
4134
4150
  pos += len + 1;
4135
4151
  }
4136
4152
  if (pos <= end)
4137
4153
  nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
4138
- oracle.heightChanged = true;
4139
- 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;
4140
4158
  }
4141
4159
  else if (force || this.outdated) {
4142
4160
  this.setHeight(oracle, oracle.heightForGap(offset, offset + this.length));
@@ -4494,7 +4512,8 @@ class ViewState {
4494
4512
  this.inView = true;
4495
4513
  this.paddingTop = 0;
4496
4514
  this.paddingBottom = 0;
4497
- this.contentWidth = 0;
4515
+ this.contentDOMWidth = 0;
4516
+ this.contentDOMHeight = 0;
4498
4517
  this.editorHeight = 0;
4499
4518
  this.heightOracle = new HeightOracle;
4500
4519
  // See VP.MaxDOMHeight
@@ -4502,6 +4521,9 @@ class ViewState {
4502
4521
  this.scrollTarget = null;
4503
4522
  // Briefly set to true when printing, to disable viewport limiting
4504
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;
4505
4527
  this.visibleRanges = [];
4506
4528
  // Cursor 'assoc' is only significant when the cursor is on a line
4507
4529
  // wrap point, where it must stick to the character that it is
@@ -4514,6 +4536,7 @@ class ViewState {
4514
4536
  this.mustEnforceCursorAssoc = false;
4515
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)]);
4516
4538
  this.viewport = this.getViewport(0, null);
4539
+ this.updateViewportLines();
4517
4540
  this.updateForViewport();
4518
4541
  this.lineGaps = this.ensureLineGaps([]);
4519
4542
  this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(false)));
@@ -4524,7 +4547,7 @@ class ViewState {
4524
4547
  for (let i = 0; i <= 1; i++) {
4525
4548
  let pos = i ? main.head : main.anchor;
4526
4549
  if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
4527
- let { from, to } = this.lineAt(pos, 0);
4550
+ let { from, to } = this.lineBlockAt(pos);
4528
4551
  viewports.push(new Viewport(from, to));
4529
4552
  }
4530
4553
  }
@@ -4532,6 +4555,12 @@ class ViewState {
4532
4555
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4533
4556
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4534
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
+ }
4535
4564
  update(update, scrollTarget = null) {
4536
4565
  let prev = this.state;
4537
4566
  this.state = update.state;
@@ -4546,6 +4575,9 @@ class ViewState {
4546
4575
  if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4547
4576
  !this.viewportIsAppropriate(viewport))
4548
4577
  viewport = this.getViewport(0, scrollTarget);
4578
+ if (!update.changes.empty || (update.flags & 2 /* Height */) ||
4579
+ viewport.from != this.viewport.from || viewport.to != this.viewport.to)
4580
+ this.updateViewportLines();
4549
4581
  this.viewport = viewport;
4550
4582
  this.updateForViewport();
4551
4583
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
@@ -4557,13 +4589,17 @@ class ViewState {
4557
4589
  update.state.selection.main.empty && update.state.selection.main.assoc)
4558
4590
  this.mustEnforceCursorAssoc = true;
4559
4591
  }
4560
- measure(docView, repeated) {
4561
- let dom = docView.dom, whiteSpace = "", direction = exports.Direction.LTR;
4562
- let result = 0;
4563
- if (!repeated) {
4592
+ measure(view) {
4593
+ let dom = view.contentDOM, style = window.getComputedStyle(dom);
4594
+ let oracle = this.heightOracle;
4595
+ let whiteSpace = style.whiteSpace, direction = style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR;
4596
+ let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4597
+ let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4598
+ let result = 0, bias = 0;
4599
+ if (measureContent) {
4600
+ this.mustMeasureContent = false;
4601
+ this.contentDOMHeight = dom.clientHeight;
4564
4602
  // Vertical padding
4565
- let style = window.getComputedStyle(dom);
4566
- whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
4567
4603
  let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4568
4604
  if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4569
4605
  result |= 8 /* Geometry */;
@@ -4578,39 +4614,42 @@ class ViewState {
4578
4614
  this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4579
4615
  if (!this.inView)
4580
4616
  return 0;
4581
- let lineHeights = docView.measureVisibleLineHeights();
4582
- let refresh = false, bias = 0, oracle = this.heightOracle;
4583
- if (!repeated) {
4584
- let contentWidth = docView.dom.clientWidth;
4585
- if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
4586
- oracle.lineWrapping && Math.abs(contentWidth - this.contentWidth) > oracle.charWidth) {
4587
- let { lineHeight, charWidth } = docView.measureTextSize();
4617
+ if (measureContent) {
4618
+ let lineHeights = view.docView.measureVisibleLineHeights();
4619
+ if (oracle.mustRefreshForHeights(lineHeights))
4620
+ refresh = true;
4621
+ let contentWidth = dom.clientWidth;
4622
+ if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4623
+ let { lineHeight, charWidth } = view.docView.measureTextSize();
4588
4624
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4589
4625
  if (refresh) {
4590
- docView.minWidth = 0;
4626
+ view.docView.minWidth = 0;
4591
4627
  result |= 8 /* Geometry */;
4592
4628
  }
4593
4629
  }
4594
- if (this.contentWidth != contentWidth) {
4595
- this.contentWidth = contentWidth;
4630
+ if (this.contentDOMWidth != contentWidth) {
4631
+ this.contentDOMWidth = contentWidth;
4596
4632
  result |= 8 /* Geometry */;
4597
4633
  }
4598
- if (this.editorHeight != docView.view.scrollDOM.clientHeight) {
4599
- this.editorHeight = docView.view.scrollDOM.clientHeight;
4634
+ if (this.editorHeight != view.scrollDOM.clientHeight) {
4635
+ this.editorHeight = view.scrollDOM.clientHeight;
4600
4636
  result |= 8 /* Geometry */;
4601
4637
  }
4602
4638
  if (dTop > 0 && dBottom > 0)
4603
4639
  bias = Math.max(dTop, dBottom);
4604
4640
  else if (dTop < 0 && dBottom < 0)
4605
4641
  bias = Math.min(dTop, dBottom);
4606
- }
4607
- oracle.heightChanged = false;
4608
- this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4609
- if (oracle.heightChanged)
4610
- result |= 2 /* Height */;
4611
- if (!this.viewportIsAppropriate(this.viewport, bias) ||
4612
- this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4642
+ oracle.heightChanged = false;
4643
+ this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4644
+ if (oracle.heightChanged)
4645
+ result |= 2 /* Height */;
4646
+ }
4647
+ let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
4648
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4649
+ if (viewportChange)
4613
4650
  this.viewport = this.getViewport(bias, this.scrollTarget);
4651
+ if ((result & 2 /* Height */) || viewportChange)
4652
+ this.updateViewportLines();
4614
4653
  this.updateForViewport();
4615
4654
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4616
4655
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
@@ -4621,12 +4660,12 @@ class ViewState {
4621
4660
  // to a line end is going to trigger a layout anyway, so it
4622
4661
  // can't be a pure write. It should be rare that it does any
4623
4662
  // writing.
4624
- docView.enforceCursorAssoc();
4663
+ view.docView.enforceCursorAssoc();
4625
4664
  }
4626
4665
  return result;
4627
4666
  }
4628
- get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4629
- get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4667
+ get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
4668
+ get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
4630
4669
  getViewport(bias, scrollTarget) {
4631
4670
  // This will divide VP.Margin between the top and the
4632
4671
  // bottom, depending on the bias (the change in viewport position
@@ -4686,12 +4725,12 @@ class ViewState {
4686
4725
  // This won't work at all in predominantly right-to-left text.
4687
4726
  if (this.heightOracle.direction != exports.Direction.LTR)
4688
4727
  return gaps;
4689
- this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4728
+ for (let line of this.viewportLines) {
4690
4729
  if (line.length < 4000 /* DoubleMargin */)
4691
- return;
4730
+ continue;
4692
4731
  let structure = lineStructure(line.from, line.to, this.state);
4693
4732
  if (structure.total < 4000 /* DoubleMargin */)
4694
- return;
4733
+ continue;
4695
4734
  let viewFrom, viewTo;
4696
4735
  if (this.heightOracle.lineWrapping) {
4697
4736
  let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
@@ -4721,7 +4760,7 @@ class ViewState {
4721
4760
  Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4722
4761
  new LineGap(from, to, this.gapSize(line, from, to, structure)));
4723
4762
  }
4724
- });
4763
+ }
4725
4764
  return gaps;
4726
4765
  }
4727
4766
  gapSize(line, from, to, structure) {
@@ -4753,27 +4792,18 @@ class ViewState {
4753
4792
  this.visibleRanges = ranges;
4754
4793
  return changed ? 4 /* Viewport */ : 0;
4755
4794
  }
4756
- lineAt(pos, editorTop) {
4757
- editorTop += this.paddingTop;
4758
- return scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, editorTop, 0), this.scaler, editorTop);
4795
+ lineBlockAt(pos) {
4796
+ return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to <= pos)) ||
4797
+ scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, 0, 0), this.scaler);
4759
4798
  }
4760
- lineAtHeight(height, editorTop) {
4761
- editorTop += this.paddingTop;
4762
- return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height, editorTop), QueryType.ByHeight, this.state.doc, editorTop, 0), this.scaler, editorTop);
4799
+ lineBlockAtHeight(height) {
4800
+ return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.state.doc, 0, 0), this.scaler);
4763
4801
  }
4764
- blockAtHeight(height, editorTop) {
4765
- editorTop += this.paddingTop;
4766
- return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height, editorTop), this.state.doc, editorTop, 0), this.scaler, editorTop);
4767
- }
4768
- forEachLine(from, to, f, editorTop) {
4769
- editorTop += this.paddingTop;
4770
- return this.heightMap.forEachLine(from, to, this.state.doc, editorTop, 0, this.scaler.scale == 1 ? f : b => f(scaleBlock(b, this.scaler, editorTop)));
4802
+ elementAtHeight(height) {
4803
+ return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4771
4804
  }
4772
4805
  get contentHeight() {
4773
- return this.domHeight + this.paddingTop + this.paddingBottom;
4774
- }
4775
- get domHeight() {
4776
- return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4806
+ return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4777
4807
  }
4778
4808
  }
4779
4809
  class Viewport {
@@ -4870,36 +4900,34 @@ class BigScaler {
4870
4900
  base = obj.bottom;
4871
4901
  }
4872
4902
  }
4873
- toDOM(n, top) {
4874
- n -= top;
4903
+ toDOM(n) {
4875
4904
  for (let i = 0, base = 0, domBase = 0;; i++) {
4876
4905
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4877
4906
  if (!vp || n < vp.top)
4878
- return domBase + (n - base) * this.scale + top;
4907
+ return domBase + (n - base) * this.scale;
4879
4908
  if (n <= vp.bottom)
4880
- return vp.domTop + (n - vp.top) + top;
4909
+ return vp.domTop + (n - vp.top);
4881
4910
  base = vp.bottom;
4882
4911
  domBase = vp.domBottom;
4883
4912
  }
4884
4913
  }
4885
- fromDOM(n, top) {
4886
- n -= top;
4914
+ fromDOM(n) {
4887
4915
  for (let i = 0, base = 0, domBase = 0;; i++) {
4888
4916
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4889
4917
  if (!vp || n < vp.domTop)
4890
- return base + (n - domBase) / this.scale + top;
4918
+ return base + (n - domBase) / this.scale;
4891
4919
  if (n <= vp.domBottom)
4892
- return vp.top + (n - vp.domTop) + top;
4920
+ return vp.top + (n - vp.domTop);
4893
4921
  base = vp.bottom;
4894
4922
  domBase = vp.domBottom;
4895
4923
  }
4896
4924
  }
4897
4925
  }
4898
- function scaleBlock(block, scaler, top) {
4926
+ function scaleBlock(block, scaler) {
4899
4927
  if (scaler.scale == 1)
4900
4928
  return block;
4901
- let bTop = scaler.toDOM(block.top, top), bBottom = scaler.toDOM(block.bottom, top);
4902
- return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler, top)) : block.type);
4929
+ let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
4930
+ return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler)) : block.type);
4903
4931
  }
4904
4932
 
4905
4933
  const theme = state.Facet.define({ combine: strs => strs.join(" ") });
@@ -5878,11 +5906,11 @@ class EditorView {
5878
5906
  for (let i = 0;; i++) {
5879
5907
  this.updateState = 1 /* Measuring */;
5880
5908
  let oldViewport = this.viewport;
5881
- let changed = this.viewState.measure(this.docView, i > 0);
5909
+ let changed = this.viewState.measure(this);
5882
5910
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5883
5911
  break;
5884
5912
  if (i > 5) {
5885
- console.warn("Viewport failed to stabilize");
5913
+ console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5886
5914
  break;
5887
5915
  }
5888
5916
  let measuring = [];
@@ -5898,7 +5926,7 @@ class EditorView {
5898
5926
  return BadMeasure;
5899
5927
  }
5900
5928
  });
5901
- let update = new ViewUpdate(this, this.state);
5929
+ let update = new ViewUpdate(this, this.state), redrawn = false;
5902
5930
  update.flags |= changed;
5903
5931
  if (!updated)
5904
5932
  updated = update;
@@ -5908,13 +5936,15 @@ class EditorView {
5908
5936
  if (!update.empty) {
5909
5937
  this.updatePlugins(update);
5910
5938
  this.inputState.update(update);
5939
+ this.updateAttrs();
5940
+ redrawn = this.docView.update(update);
5911
5941
  }
5912
- this.updateAttrs();
5913
- let redrawn = changed > 0 && this.docView.update(update);
5914
5942
  for (let i = 0; i < measuring.length; i++)
5915
5943
  if (measured[i] != BadMeasure) {
5916
5944
  try {
5917
- measuring[i].write(measured[i], this);
5945
+ let m = measuring[i];
5946
+ if (m.write)
5947
+ m.write(measured[i], this);
5918
5948
  }
5919
5949
  catch (e) {
5920
5950
  logException(this.state, e);
@@ -5924,8 +5954,8 @@ class EditorView {
5924
5954
  this.docView.scrollIntoView(this.viewState.scrollTarget);
5925
5955
  this.viewState.scrollTarget = null;
5926
5956
  }
5927
- if (changed)
5928
- this.docView.updateSelection(redrawn);
5957
+ if (redrawn)
5958
+ this.docView.updateSelection(true);
5929
5959
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5930
5960
  break;
5931
5961
  }
@@ -6038,6 +6068,13 @@ class EditorView {
6038
6068
  return null;
6039
6069
  }
6040
6070
  /**
6071
+ The top position of the document, in screen coordinates. This
6072
+ may be negative when the editor is scrolled down.
6073
+ */
6074
+ get documentTop() {
6075
+ return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
6076
+ }
6077
+ /**
6041
6078
  Find the line or block widget at the given vertical position.
6042
6079
 
6043
6080
  By default, this position is interpreted as a screen position,
@@ -6047,10 +6084,21 @@ class EditorView {
6047
6084
  position, or a precomputed document top
6048
6085
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6049
6086
  queries.
6087
+
6088
+ *Deprecated: use `blockAtHeight` instead.*
6050
6089
  */
6051
6090
  blockAtHeight(height, docTop) {
6091
+ let top = ensureTop(docTop, this);
6092
+ return this.elementAtHeight(height - top).moveY(top);
6093
+ }
6094
+ /**
6095
+ Find the text line or block widget at the given vertical
6096
+ position (which is interpreted as relative to the [top of the
6097
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)
6098
+ */
6099
+ elementAtHeight(height) {
6052
6100
  this.readMeasured();
6053
- return this.viewState.blockAtHeight(height, ensureTop(docTop, this.contentDOM));
6101
+ return this.viewState.elementAtHeight(height);
6054
6102
  }
6055
6103
  /**
6056
6104
  Find information for the visual line (see
@@ -6062,20 +6110,43 @@ class EditorView {
6062
6110
  Defaults to treating `height` as a screen position. See
6063
6111
  [`blockAtHeight`](https://codemirror.net/6/docs/ref/#view.EditorView.blockAtHeight) for the
6064
6112
  interpretation of the `docTop` parameter.
6113
+
6114
+ *Deprecated: use `lineBlockAtHeight` instead.*
6065
6115
  */
6066
6116
  visualLineAtHeight(height, docTop) {
6117
+ let top = ensureTop(docTop, this);
6118
+ return this.lineBlockAtHeight(height - top).moveY(top);
6119
+ }
6120
+ /**
6121
+ Find the line block (see
6122
+ [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
6123
+ height.
6124
+ */
6125
+ lineBlockAtHeight(height) {
6067
6126
  this.readMeasured();
6068
- return this.viewState.lineAtHeight(height, ensureTop(docTop, this.contentDOM));
6127
+ return this.viewState.lineBlockAtHeight(height);
6069
6128
  }
6070
6129
  /**
6071
6130
  Iterate over the height information of the visual lines in the
6072
6131
  viewport. The heights of lines are reported relative to the
6073
6132
  given document top, which defaults to the screen position of the
6074
6133
  document (forcing a layout).
6134
+
6135
+ *Deprecated: use `viewportLineBlocks` instead.*
6075
6136
  */
6076
6137
  viewportLines(f, docTop) {
6077
- let { from, to } = this.viewport;
6078
- this.viewState.forEachLine(from, to, f, ensureTop(docTop, this.contentDOM));
6138
+ let top = ensureTop(docTop, this);
6139
+ for (let line of this.viewportLineBlocks)
6140
+ f(line.moveY(top));
6141
+ }
6142
+ /**
6143
+ Get the extent and vertical position of all [line
6144
+ blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
6145
+ are relative to the [top of the
6146
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
6147
+ */
6148
+ get viewportLineBlocks() {
6149
+ return this.viewState.viewportLines;
6079
6150
  }
6080
6151
  /**
6081
6152
  Find the extent and height of the visual line (a range delimited
@@ -6086,9 +6157,22 @@ class EditorView {
6086
6157
  argument, which defaults to 0 for this method. You can pass
6087
6158
  `view.contentDOM.getBoundingClientRect().top` here to get screen
6088
6159
  coordinates.
6160
+
6161
+ *Deprecated: use `lineBlockAt` instead.*
6089
6162
  */
6090
6163
  visualLineAt(pos, docTop = 0) {
6091
- return this.viewState.lineAt(pos, docTop);
6164
+ return this.lineBlockAt(pos).moveY(docTop + this.viewState.paddingTop);
6165
+ }
6166
+ /**
6167
+ Find the line block around the given document position. A line
6168
+ block is a range delimited on both sides by either a
6169
+ non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range) line breaks, or the
6170
+ start/end of the document. It will usually just hold a line of
6171
+ text, but may be broken into multiple textblocks by block
6172
+ widgets.
6173
+ */
6174
+ lineBlockAt(pos) {
6175
+ return this.viewState.lineBlockAt(pos);
6092
6176
  }
6093
6177
  /**
6094
6178
  The editor's total content height.
@@ -6421,8 +6505,9 @@ search match).
6421
6505
  EditorView.announce = state.StateEffect.define();
6422
6506
  // Maximum line length for which we compute accurate bidi info
6423
6507
  const MaxBidiLine = 4096;
6424
- function ensureTop(given, dom) {
6425
- return given == null ? dom.getBoundingClientRect().top : given;
6508
+ // FIXME remove this and its callers on next breaking release
6509
+ function ensureTop(given, view) {
6510
+ return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6426
6511
  }
6427
6512
  let resizeDebounce = -1;
6428
6513
  function ensureGlobalHandler() {
@@ -6773,7 +6858,7 @@ function wrappedLine(view, pos, inside) {
6773
6858
  type: exports.BlockType.Text };
6774
6859
  }
6775
6860
  function blockAt(view, pos) {
6776
- let line = view.visualLineAt(pos);
6861
+ let line = view.lineBlockAt(pos);
6777
6862
  if (Array.isArray(line.type))
6778
6863
  for (let l of line.type) {
6779
6864
  if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
@@ -7164,7 +7249,7 @@ const activeLineHighlighter = ViewPlugin.fromClass(class {
7164
7249
  for (let r of view.state.selection.ranges) {
7165
7250
  if (!r.empty)
7166
7251
  return Decoration.none;
7167
- let line = view.visualLineAt(r.head);
7252
+ let line = view.lineBlockAt(r.head);
7168
7253
  if (line.from > lastLineStart) {
7169
7254
  deco.push(lineDeco.range(line.from));
7170
7255
  lastLineStart = line.from;