@codemirror/view 6.6.0 → 6.7.0

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,23 @@
1
+ ## 6.7.0 (2022-12-07)
2
+
3
+ ### Bug fixes
4
+
5
+ Make the editor notice widget height changes to automatically adjust its height information.
6
+
7
+ Fix an issue where widget buffers could be incorrectly omitted after empty lines.
8
+
9
+ Fix an issue in content redrawing that could cause `coordsAtPos` to return incorrect results.
10
+
11
+ ### New features
12
+
13
+ The static `RectangleMarker.forRange` method exposes the logic used by the editor to draw rectangles covering a selection range.
14
+
15
+ Layers can now provide a `destroy` function to be called when the layer is removed.
16
+
17
+ The new `highlightWhitespace` extension makes spaces and tabs in the editor visible.
18
+
19
+ The `highlightTrailingWhitespace` extension can be used to make trailing whitespace stand out.
20
+
1
21
  ## 6.6.0 (2022-11-24)
2
22
 
3
23
  ### New features
package/dist/index.cjs CHANGED
@@ -1603,6 +1603,7 @@ class ContentBuilder {
1603
1603
  this.curLine = null;
1604
1604
  this.breakAtStart = 0;
1605
1605
  this.pendingBuffer = 0 /* Buf.No */;
1606
+ this.bufferMarks = [];
1606
1607
  // Set to false directly after a widget that covers the position after it
1607
1608
  this.atCursorPos = true;
1608
1609
  this.openStart = -1;
@@ -1625,20 +1626,20 @@ class ContentBuilder {
1625
1626
  }
1626
1627
  return this.curLine;
1627
1628
  }
1628
- flushBuffer(active) {
1629
+ flushBuffer(active = this.bufferMarks) {
1629
1630
  if (this.pendingBuffer) {
1630
1631
  this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1631
1632
  this.pendingBuffer = 0 /* Buf.No */;
1632
1633
  }
1633
1634
  }
1634
1635
  addBlockWidget(view) {
1635
- this.flushBuffer([]);
1636
+ this.flushBuffer();
1636
1637
  this.curLine = null;
1637
1638
  this.content.push(view);
1638
1639
  }
1639
1640
  finish(openEnd) {
1640
- if (!openEnd)
1641
- this.flushBuffer([]);
1641
+ if (this.pendingBuffer && openEnd <= this.bufferMarks.length)
1642
+ this.flushBuffer();
1642
1643
  else
1643
1644
  this.pendingBuffer = 0 /* Buf.No */;
1644
1645
  if (!this.posCovered())
@@ -1658,8 +1659,9 @@ class ContentBuilder {
1658
1659
  this.content[this.content.length - 1].breakAfter = 1;
1659
1660
  else
1660
1661
  this.breakAtStart = 1;
1661
- this.flushBuffer([]);
1662
+ this.flushBuffer();
1662
1663
  this.curLine = null;
1664
+ this.atCursorPos = true;
1663
1665
  length--;
1664
1666
  continue;
1665
1667
  }
@@ -1701,7 +1703,7 @@ class ContentBuilder {
1701
1703
  else {
1702
1704
  let view = WidgetView.create(deco.widget || new NullWidget("span"), len, len ? 0 : deco.startSide);
1703
1705
  let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1704
- let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1706
+ let cursorAfter = !view.isEditable && (from < to || openStart > active.length || deco.startSide <= 0);
1705
1707
  let line = this.getLine();
1706
1708
  if (this.pendingBuffer == 2 /* Buf.IfCursor */ && !cursorBefore)
1707
1709
  this.pendingBuffer = 0 /* Buf.No */;
@@ -1712,7 +1714,9 @@ class ContentBuilder {
1712
1714
  }
1713
1715
  line.append(wrapMarks(view, active), openStart);
1714
1716
  this.atCursorPos = cursorAfter;
1715
- this.pendingBuffer = !cursorAfter ? 0 /* Buf.No */ : from < to ? 1 /* Buf.Yes */ : 2 /* Buf.IfCursor */;
1717
+ this.pendingBuffer = !cursorAfter ? 0 /* Buf.No */ : from < to || openStart > active.length ? 1 /* Buf.Yes */ : 2 /* Buf.IfCursor */;
1718
+ if (this.pendingBuffer)
1719
+ this.bufferMarks = active.slice();
1716
1720
  }
1717
1721
  }
1718
1722
  else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
@@ -5291,7 +5295,6 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5291
5295
  margin: 0,
5292
5296
  flexGrow: 2,
5293
5297
  flexShrink: 0,
5294
- minHeight: "100%",
5295
5298
  display: "block",
5296
5299
  whiteSpace: "pre",
5297
5300
  wordWrap: "normal",
@@ -5438,6 +5441,21 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5438
5441
  display: "inline-block",
5439
5442
  verticalAlign: "top",
5440
5443
  },
5444
+ ".cm-highlightSpace:before": {
5445
+ content: "attr(data-display)",
5446
+ position: "absolute",
5447
+ pointerEvents: "none",
5448
+ color: "#888"
5449
+ },
5450
+ ".cm-highlightTab": {
5451
+ backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23888" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,
5452
+ backgroundSize: "auto 100%",
5453
+ backgroundPosition: "right 90%",
5454
+ backgroundRepeat: "no-repeat"
5455
+ },
5456
+ ".cm-trailingSpace": {
5457
+ backgroundColor: "#ff332255"
5458
+ },
5441
5459
  ".cm-button": {
5442
5460
  verticalAlign: "middle",
5443
5461
  color: "inherit",
@@ -5740,7 +5758,8 @@ class DOMObserver {
5740
5758
  this.lastChange = 0;
5741
5759
  this.scrollTargets = [];
5742
5760
  this.intersection = null;
5743
- this.resize = null;
5761
+ this.resizeScroll = null;
5762
+ this.resizeContent = null;
5744
5763
  this.intersecting = false;
5745
5764
  this.gapIntersection = null;
5746
5765
  this.gaps = [];
@@ -5778,12 +5797,14 @@ class DOMObserver {
5778
5797
  this.onPrint = this.onPrint.bind(this);
5779
5798
  this.onScroll = this.onScroll.bind(this);
5780
5799
  if (typeof ResizeObserver == "function") {
5781
- this.resize = new ResizeObserver(() => {
5800
+ this.resizeScroll = new ResizeObserver(() => {
5782
5801
  var _a;
5783
5802
  if (((_a = this.view.docView) === null || _a === void 0 ? void 0 : _a.lastUpdate) < Date.now() - 75)
5784
5803
  this.onResize();
5785
5804
  });
5786
- this.resize.observe(view.scrollDOM);
5805
+ this.resizeScroll.observe(view.scrollDOM);
5806
+ this.resizeContent = new ResizeObserver(() => this.view.requestMeasure());
5807
+ this.resizeContent.observe(view.contentDOM);
5787
5808
  }
5788
5809
  this.addWindowListeners(this.win = view.win);
5789
5810
  this.start();
@@ -6102,11 +6123,12 @@ class DOMObserver {
6102
6123
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6103
6124
  }
6104
6125
  destroy() {
6105
- var _a, _b, _c;
6126
+ var _a, _b, _c, _d;
6106
6127
  this.stop();
6107
6128
  (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
6108
6129
  (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
6109
- (_c = this.resize) === null || _c === void 0 ? void 0 : _c.disconnect();
6130
+ (_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
6131
+ (_d = this.resizeContent) === null || _d === void 0 ? void 0 : _d.disconnect();
6110
6132
  for (let dom of this.scrollTargets)
6111
6133
  dom.removeEventListener("scroll", this.onScroll);
6112
6134
  this.removeWindowListeners(this.win);
@@ -6205,7 +6227,7 @@ class EditorView {
6205
6227
  this.scrollDOM.className = "cm-scroller";
6206
6228
  this.scrollDOM.appendChild(this.contentDOM);
6207
6229
  this.announceDOM = document.createElement("div");
6208
- this.announceDOM.style.cssText = "position: absolute; top: -10000px";
6230
+ this.announceDOM.style.cssText = "position: fixed; top: -10000px";
6209
6231
  this.announceDOM.setAttribute("aria-live", "polite");
6210
6232
  this.dom = document.createElement("div");
6211
6233
  this.dom.appendChild(this.announceDOM);
@@ -7296,7 +7318,8 @@ a rectangle at a given set of coordinates.
7296
7318
  */
7297
7319
  class RectangleMarker {
7298
7320
  /**
7299
- Create a marker with the given class and dimensions.
7321
+ Create a marker with the given class and dimensions. If `width`
7322
+ is null, the DOM element will get no width style.
7300
7323
  */
7301
7324
  constructor(className, left, top, width, height) {
7302
7325
  this.className = className;
@@ -7320,7 +7343,7 @@ class RectangleMarker {
7320
7343
  adjust(elt) {
7321
7344
  elt.style.left = this.left + "px";
7322
7345
  elt.style.top = this.top + "px";
7323
- if (this.width >= 0)
7346
+ if (this.width != null)
7324
7347
  elt.style.width = this.width + "px";
7325
7348
  elt.style.height = this.height + "px";
7326
7349
  }
@@ -7328,6 +7351,129 @@ class RectangleMarker {
7328
7351
  return this.left == p.left && this.top == p.top && this.width == p.width && this.height == p.height &&
7329
7352
  this.className == p.className;
7330
7353
  }
7354
+ /**
7355
+ Create a set of rectangles for the given selection range,
7356
+ assigning them theclass`className`. Will create a single
7357
+ rectangle for empty ranges, and a set of selection-style
7358
+ rectangles covering the range's content (in a bidi-aware
7359
+ way) for non-empty ones.
7360
+ */
7361
+ static forRange(view, className, range) {
7362
+ if (range.empty) {
7363
+ let pos = view.coordsAtPos(range.head, range.assoc || 1);
7364
+ if (!pos)
7365
+ return [];
7366
+ let base = getBase(view);
7367
+ return [new RectangleMarker(className, pos.left - base.left, pos.top - base.top, null, pos.bottom - pos.top)];
7368
+ }
7369
+ else {
7370
+ return rectanglesForRange(view, className, range);
7371
+ }
7372
+ }
7373
+ }
7374
+ function getBase(view) {
7375
+ let rect = view.scrollDOM.getBoundingClientRect();
7376
+ let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
7377
+ return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop };
7378
+ }
7379
+ function wrappedLine(view, pos, inside) {
7380
+ let range = state.EditorSelection.cursor(pos);
7381
+ return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
7382
+ to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
7383
+ type: exports.BlockType.Text };
7384
+ }
7385
+ function blockAt(view, pos) {
7386
+ let line = view.lineBlockAt(pos);
7387
+ if (Array.isArray(line.type))
7388
+ for (let l of line.type) {
7389
+ if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
7390
+ return l;
7391
+ }
7392
+ return line;
7393
+ }
7394
+ function rectanglesForRange(view, className, range) {
7395
+ if (range.to <= view.viewport.from || range.from >= view.viewport.to)
7396
+ return [];
7397
+ let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
7398
+ let ltr = view.textDirection == exports.Direction.LTR;
7399
+ let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
7400
+ let lineStyle = window.getComputedStyle(content.firstChild);
7401
+ let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
7402
+ let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
7403
+ let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
7404
+ let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
7405
+ let visualEnd = endBlock.type == exports.BlockType.Text ? endBlock : null;
7406
+ if (view.lineWrapping) {
7407
+ if (visualStart)
7408
+ visualStart = wrappedLine(view, from, visualStart);
7409
+ if (visualEnd)
7410
+ visualEnd = wrappedLine(view, to, visualEnd);
7411
+ }
7412
+ if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
7413
+ return pieces(drawForLine(range.from, range.to, visualStart));
7414
+ }
7415
+ else {
7416
+ let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
7417
+ let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
7418
+ let between = [];
7419
+ if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
7420
+ between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
7421
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
7422
+ top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
7423
+ return pieces(top).concat(between).concat(pieces(bottom));
7424
+ }
7425
+ function piece(left, top, right, bottom) {
7426
+ return new RectangleMarker(className, left - base.left, top - base.top - 0.01 /* C.Epsilon */, right - left, bottom - top + 0.01 /* C.Epsilon */);
7427
+ }
7428
+ function pieces({ top, bottom, horizontal }) {
7429
+ let pieces = [];
7430
+ for (let i = 0; i < horizontal.length; i += 2)
7431
+ pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
7432
+ return pieces;
7433
+ }
7434
+ // Gets passed from/to in line-local positions
7435
+ function drawForLine(from, to, line) {
7436
+ let top = 1e9, bottom = -1e9, horizontal = [];
7437
+ function addSpan(from, fromOpen, to, toOpen, dir) {
7438
+ // Passing 2/-2 is a kludge to force the view to return
7439
+ // coordinates on the proper side of block widgets, since
7440
+ // normalizing the side there, though appropriate for most
7441
+ // coordsAtPos queries, would break selection drawing.
7442
+ let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7443
+ let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7444
+ top = Math.min(fromCoords.top, toCoords.top, top);
7445
+ bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7446
+ if (dir == exports.Direction.LTR)
7447
+ horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
7448
+ else
7449
+ horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
7450
+ }
7451
+ let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
7452
+ // Split the range by visible range and document line
7453
+ for (let r of view.visibleRanges)
7454
+ if (r.to > start && r.from < end) {
7455
+ for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
7456
+ let docLine = view.state.doc.lineAt(pos);
7457
+ for (let span of view.bidiSpans(docLine)) {
7458
+ let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
7459
+ if (spanFrom >= endPos)
7460
+ break;
7461
+ if (spanTo > pos)
7462
+ addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
7463
+ }
7464
+ pos = docLine.to + 1;
7465
+ if (pos >= endPos)
7466
+ break;
7467
+ }
7468
+ }
7469
+ if (horizontal.length == 0)
7470
+ addSpan(start, from == null, end, to == null, view.textDirection);
7471
+ return { top, bottom, horizontal };
7472
+ }
7473
+ function drawForWidget(block, top) {
7474
+ let y = contentRect.top + (top ? block.top : block.bottom);
7475
+ return { top: y, bottom: y, horizontal: [] };
7476
+ }
7331
7477
  }
7332
7478
  function sameMarker(a, b) {
7333
7479
  return a.constructor == b.constructor && a.eq(b);
@@ -7387,6 +7533,8 @@ class LayerView {
7387
7533
  }
7388
7534
  }
7389
7535
  destroy() {
7536
+ if (this.layer.destroy)
7537
+ this.layer.destroy(this.dom, this.view);
7390
7538
  this.dom.remove();
7391
7539
  }
7392
7540
  }
@@ -7446,13 +7594,14 @@ function configChanged(update) {
7446
7594
  const cursorLayer = layer({
7447
7595
  above: true,
7448
7596
  markers(view) {
7449
- let { state } = view, conf = state.facet(selectionConfig);
7597
+ let { state: state$1 } = view, conf = state$1.facet(selectionConfig);
7450
7598
  let cursors = [];
7451
- for (let r of state.selection.ranges) {
7452
- let prim = r == state.selection.main;
7599
+ for (let r of state$1.selection.ranges) {
7600
+ let prim = r == state$1.selection.main;
7453
7601
  if (r.empty ? !prim || CanHidePrimary : conf.drawRangeCursor) {
7454
- let piece = measureCursor(view, r, prim);
7455
- if (piece)
7602
+ let className = prim ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary";
7603
+ let cursor = r.empty ? r : state.EditorSelection.cursor(r.head, r.head > r.anchor ? -1 : 1);
7604
+ for (let piece of RectangleMarker.forRange(view, className, cursor))
7456
7605
  cursors.push(piece);
7457
7606
  }
7458
7607
  }
@@ -7477,7 +7626,8 @@ function setBlinkRate(state, dom) {
7477
7626
  const selectionLayer = layer({
7478
7627
  above: false,
7479
7628
  markers(view) {
7480
- return view.state.selection.ranges.map(r => r.empty ? [] : measureRange(view, r)).reduce((a, b) => a.concat(b));
7629
+ return view.state.selection.ranges.map(r => r.empty ? [] : RectangleMarker.forRange(view, "cm-selectionBackground", r))
7630
+ .reduce((a, b) => a.concat(b));
7481
7631
  },
7482
7632
  update(update, dom) {
7483
7633
  return update.docChanged || update.selectionSet || update.viewportChanged || configChanged(update);
@@ -7493,117 +7643,6 @@ const themeSpec = {
7493
7643
  if (CanHidePrimary)
7494
7644
  themeSpec[".cm-line"].caretColor = "transparent !important";
7495
7645
  const hideNativeSelection = state.Prec.highest(EditorView.theme(themeSpec));
7496
- function getBase(view) {
7497
- let rect = view.scrollDOM.getBoundingClientRect();
7498
- let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
7499
- return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop };
7500
- }
7501
- function wrappedLine(view, pos, inside) {
7502
- let range = state.EditorSelection.cursor(pos);
7503
- return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
7504
- to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
7505
- type: exports.BlockType.Text };
7506
- }
7507
- function blockAt(view, pos) {
7508
- let line = view.lineBlockAt(pos);
7509
- if (Array.isArray(line.type))
7510
- for (let l of line.type) {
7511
- if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
7512
- return l;
7513
- }
7514
- return line;
7515
- }
7516
- function measureRange(view, range) {
7517
- if (range.to <= view.viewport.from || range.from >= view.viewport.to)
7518
- return [];
7519
- let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
7520
- let ltr = view.textDirection == exports.Direction.LTR;
7521
- let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
7522
- let lineStyle = window.getComputedStyle(content.firstChild);
7523
- let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
7524
- let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
7525
- let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
7526
- let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
7527
- let visualEnd = endBlock.type == exports.BlockType.Text ? endBlock : null;
7528
- if (view.lineWrapping) {
7529
- if (visualStart)
7530
- visualStart = wrappedLine(view, from, visualStart);
7531
- if (visualEnd)
7532
- visualEnd = wrappedLine(view, to, visualEnd);
7533
- }
7534
- if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
7535
- return pieces(drawForLine(range.from, range.to, visualStart));
7536
- }
7537
- else {
7538
- let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
7539
- let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
7540
- let between = [];
7541
- if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
7542
- between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
7543
- else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
7544
- top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
7545
- return pieces(top).concat(between).concat(pieces(bottom));
7546
- }
7547
- function piece(left, top, right, bottom) {
7548
- return new RectangleMarker("cm-selectionBackground", left - base.left, top - base.top - 0.01 /* C.Epsilon */, right - left, bottom - top + 0.01 /* C.Epsilon */);
7549
- }
7550
- function pieces({ top, bottom, horizontal }) {
7551
- let pieces = [];
7552
- for (let i = 0; i < horizontal.length; i += 2)
7553
- pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
7554
- return pieces;
7555
- }
7556
- // Gets passed from/to in line-local positions
7557
- function drawForLine(from, to, line) {
7558
- let top = 1e9, bottom = -1e9, horizontal = [];
7559
- function addSpan(from, fromOpen, to, toOpen, dir) {
7560
- // Passing 2/-2 is a kludge to force the view to return
7561
- // coordinates on the proper side of block widgets, since
7562
- // normalizing the side there, though appropriate for most
7563
- // coordsAtPos queries, would break selection drawing.
7564
- let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7565
- let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7566
- top = Math.min(fromCoords.top, toCoords.top, top);
7567
- bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7568
- if (dir == exports.Direction.LTR)
7569
- horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
7570
- else
7571
- horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
7572
- }
7573
- let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
7574
- // Split the range by visible range and document line
7575
- for (let r of view.visibleRanges)
7576
- if (r.to > start && r.from < end) {
7577
- for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
7578
- let docLine = view.state.doc.lineAt(pos);
7579
- for (let span of view.bidiSpans(docLine)) {
7580
- let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
7581
- if (spanFrom >= endPos)
7582
- break;
7583
- if (spanTo > pos)
7584
- addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
7585
- }
7586
- pos = docLine.to + 1;
7587
- if (pos >= endPos)
7588
- break;
7589
- }
7590
- }
7591
- if (horizontal.length == 0)
7592
- addSpan(start, from == null, end, to == null, view.textDirection);
7593
- return { top, bottom, horizontal };
7594
- }
7595
- function drawForWidget(block, top) {
7596
- let y = contentRect.top + (top ? block.top : block.bottom);
7597
- return { top: y, bottom: y, horizontal: [] };
7598
- }
7599
- }
7600
- function measureCursor(view, cursor, primary) {
7601
- let pos = view.coordsAtPos(cursor.head, cursor.assoc || 1);
7602
- if (!pos)
7603
- return null;
7604
- let base = getBase(view);
7605
- return new RectangleMarker(primary ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary", pos.left - base.left, pos.top - base.top, -1, pos.bottom - pos.top);
7606
- }
7607
7646
 
7608
7647
  const setDropCursorPos = state.StateEffect.define({
7609
7648
  map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
@@ -9370,6 +9409,57 @@ function highlightActiveLineGutter() {
9370
9409
  return activeLineGutterHighlighter;
9371
9410
  }
9372
9411
 
9412
+ const WhitespaceDeco = new Map();
9413
+ function getWhitespaceDeco(space) {
9414
+ let deco = WhitespaceDeco.get(space);
9415
+ if (!deco)
9416
+ WhitespaceDeco.set(space, deco = Decoration.mark({
9417
+ attributes: space === "\t" ? {
9418
+ class: "cm-highlightTab",
9419
+ } : {
9420
+ class: "cm-highlightSpace",
9421
+ "data-display": space.replace(/ /g, "·")
9422
+ }
9423
+ }));
9424
+ return deco;
9425
+ }
9426
+ function matcher(decorator) {
9427
+ return ViewPlugin.define(view => ({
9428
+ decorations: decorator.createDeco(view),
9429
+ update(u) {
9430
+ this.decorations = decorator.updateDeco(u, this.decorations);
9431
+ },
9432
+ }), {
9433
+ decorations: v => v.decorations
9434
+ });
9435
+ }
9436
+ const whitespaceHighlighter = matcher(new MatchDecorator({
9437
+ regexp: /\t| +/g,
9438
+ decoration: match => getWhitespaceDeco(match[0]),
9439
+ boundary: /\S/,
9440
+ }));
9441
+ /**
9442
+ Returns an extension that highlights whitespace, adding a
9443
+ `cm-highlightSpace` class to stretches of spaces, and a
9444
+ `cm-highlightTab` class to individual tab characters. By default,
9445
+ the former are shown as faint dots, and the latter as arrows.
9446
+ */
9447
+ function highlightWhitespace() {
9448
+ return whitespaceHighlighter;
9449
+ }
9450
+ const trailingHighlighter = matcher(new MatchDecorator({
9451
+ regexp: /\s+$/g,
9452
+ decoration: Decoration.mark({ class: "cm-trailingSpace" }),
9453
+ boundary: /\S/,
9454
+ }));
9455
+ /**
9456
+ Returns an extension that adds a `cm-trailingSpace` class to all
9457
+ trailing whitespace.
9458
+ */
9459
+ function highlightTrailingWhitespace() {
9460
+ return trailingHighlighter;
9461
+ }
9462
+
9373
9463
  /**
9374
9464
  @internal
9375
9465
  */
@@ -9399,6 +9489,8 @@ exports.hasHoverTooltips = hasHoverTooltips;
9399
9489
  exports.highlightActiveLine = highlightActiveLine;
9400
9490
  exports.highlightActiveLineGutter = highlightActiveLineGutter;
9401
9491
  exports.highlightSpecialChars = highlightSpecialChars;
9492
+ exports.highlightTrailingWhitespace = highlightTrailingWhitespace;
9493
+ exports.highlightWhitespace = highlightWhitespace;
9402
9494
  exports.hoverTooltip = hoverTooltip;
9403
9495
  exports.keymap = keymap;
9404
9496
  exports.layer = layer;
package/dist/index.d.ts CHANGED
@@ -1402,13 +1402,22 @@ declare class RectangleMarker implements LayerMarker {
1402
1402
  private width;
1403
1403
  private height;
1404
1404
  /**
1405
- Create a marker with the given class and dimensions.
1405
+ Create a marker with the given class and dimensions. If `width`
1406
+ is null, the DOM element will get no width style.
1406
1407
  */
1407
- constructor(className: string, left: number, top: number, width: number, height: number);
1408
+ constructor(className: string, left: number, top: number, width: number | null, height: number);
1408
1409
  draw(): HTMLDivElement;
1409
1410
  update(elt: HTMLElement, prev: RectangleMarker): boolean;
1410
1411
  private adjust;
1411
1412
  eq(p: RectangleMarker): boolean;
1413
+ /**
1414
+ Create a set of rectangles for the given selection range,
1415
+ assigning them theclass`className`. Will create a single
1416
+ rectangle for empty ranges, and a set of selection-style
1417
+ rectangles covering the range's content (in a bidi-aware
1418
+ way) for non-empty ones.
1419
+ */
1420
+ static forRange(view: EditorView, className: string, range: SelectionRange): readonly RectangleMarker[];
1412
1421
  }
1413
1422
  interface LayerConfig {
1414
1423
  /**
@@ -1434,6 +1443,11 @@ interface LayerConfig {
1434
1443
  If given, this is called when the layer is created.
1435
1444
  */
1436
1445
  mount?(layer: HTMLElement, view: EditorView): void;
1446
+ /**
1447
+ If given, called when the layer is removed from the editor or
1448
+ the entire editor is destroyed.
1449
+ */
1450
+ destroy?(layer: HTMLElement, view: EditorView): void;
1437
1451
  }
1438
1452
  /**
1439
1453
  Define a layer.
@@ -1900,4 +1914,17 @@ line](https://codemirror.net/6/docs/ref/#view.highlightActiveLine).
1900
1914
  */
1901
1915
  declare function highlightActiveLineGutter(): Extension;
1902
1916
 
1903
- export { BidiSpan, BlockInfo, BlockType, Command, DOMEventHandlers, DOMEventMap, Decoration, DecorationSet, Direction, EditorView, EditorViewConfig, GutterMarker, KeyBinding, LayerMarker, MatchDecorator, MouseSelectionStyle, Panel, PanelConstructor, PluginSpec, PluginValue, Rect, RectangleMarker, Tooltip, TooltipView, ViewPlugin, ViewUpdate, WidgetType, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getPanel, getTooltip, gutter, gutterLineClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showPanel, showTooltip, tooltips };
1917
+ /**
1918
+ Returns an extension that highlights whitespace, adding a
1919
+ `cm-highlightSpace` class to stretches of spaces, and a
1920
+ `cm-highlightTab` class to individual tab characters. By default,
1921
+ the former are shown as faint dots, and the latter as arrows.
1922
+ */
1923
+ declare function highlightWhitespace(): Extension;
1924
+ /**
1925
+ Returns an extension that adds a `cm-trailingSpace` class to all
1926
+ trailing whitespace.
1927
+ */
1928
+ declare function highlightTrailingWhitespace(): Extension;
1929
+
1930
+ export { BidiSpan, BlockInfo, BlockType, Command, DOMEventHandlers, DOMEventMap, Decoration, DecorationSet, Direction, EditorView, EditorViewConfig, GutterMarker, KeyBinding, LayerMarker, MatchDecorator, MouseSelectionStyle, Panel, PanelConstructor, PluginSpec, PluginValue, Rect, RectangleMarker, Tooltip, TooltipView, ViewPlugin, ViewUpdate, WidgetType, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getPanel, getTooltip, gutter, gutterLineClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showPanel, showTooltip, tooltips };
package/dist/index.js CHANGED
@@ -1598,6 +1598,7 @@ class ContentBuilder {
1598
1598
  this.curLine = null;
1599
1599
  this.breakAtStart = 0;
1600
1600
  this.pendingBuffer = 0 /* Buf.No */;
1601
+ this.bufferMarks = [];
1601
1602
  // Set to false directly after a widget that covers the position after it
1602
1603
  this.atCursorPos = true;
1603
1604
  this.openStart = -1;
@@ -1620,20 +1621,20 @@ class ContentBuilder {
1620
1621
  }
1621
1622
  return this.curLine;
1622
1623
  }
1623
- flushBuffer(active) {
1624
+ flushBuffer(active = this.bufferMarks) {
1624
1625
  if (this.pendingBuffer) {
1625
1626
  this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1626
1627
  this.pendingBuffer = 0 /* Buf.No */;
1627
1628
  }
1628
1629
  }
1629
1630
  addBlockWidget(view) {
1630
- this.flushBuffer([]);
1631
+ this.flushBuffer();
1631
1632
  this.curLine = null;
1632
1633
  this.content.push(view);
1633
1634
  }
1634
1635
  finish(openEnd) {
1635
- if (!openEnd)
1636
- this.flushBuffer([]);
1636
+ if (this.pendingBuffer && openEnd <= this.bufferMarks.length)
1637
+ this.flushBuffer();
1637
1638
  else
1638
1639
  this.pendingBuffer = 0 /* Buf.No */;
1639
1640
  if (!this.posCovered())
@@ -1653,8 +1654,9 @@ class ContentBuilder {
1653
1654
  this.content[this.content.length - 1].breakAfter = 1;
1654
1655
  else
1655
1656
  this.breakAtStart = 1;
1656
- this.flushBuffer([]);
1657
+ this.flushBuffer();
1657
1658
  this.curLine = null;
1659
+ this.atCursorPos = true;
1658
1660
  length--;
1659
1661
  continue;
1660
1662
  }
@@ -1696,7 +1698,7 @@ class ContentBuilder {
1696
1698
  else {
1697
1699
  let view = WidgetView.create(deco.widget || new NullWidget("span"), len, len ? 0 : deco.startSide);
1698
1700
  let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1699
- let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1701
+ let cursorAfter = !view.isEditable && (from < to || openStart > active.length || deco.startSide <= 0);
1700
1702
  let line = this.getLine();
1701
1703
  if (this.pendingBuffer == 2 /* Buf.IfCursor */ && !cursorBefore)
1702
1704
  this.pendingBuffer = 0 /* Buf.No */;
@@ -1707,7 +1709,9 @@ class ContentBuilder {
1707
1709
  }
1708
1710
  line.append(wrapMarks(view, active), openStart);
1709
1711
  this.atCursorPos = cursorAfter;
1710
- this.pendingBuffer = !cursorAfter ? 0 /* Buf.No */ : from < to ? 1 /* Buf.Yes */ : 2 /* Buf.IfCursor */;
1712
+ this.pendingBuffer = !cursorAfter ? 0 /* Buf.No */ : from < to || openStart > active.length ? 1 /* Buf.Yes */ : 2 /* Buf.IfCursor */;
1713
+ if (this.pendingBuffer)
1714
+ this.bufferMarks = active.slice();
1711
1715
  }
1712
1716
  }
1713
1717
  else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
@@ -5284,7 +5288,6 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5284
5288
  margin: 0,
5285
5289
  flexGrow: 2,
5286
5290
  flexShrink: 0,
5287
- minHeight: "100%",
5288
5291
  display: "block",
5289
5292
  whiteSpace: "pre",
5290
5293
  wordWrap: "normal",
@@ -5431,6 +5434,21 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5431
5434
  display: "inline-block",
5432
5435
  verticalAlign: "top",
5433
5436
  },
5437
+ ".cm-highlightSpace:before": {
5438
+ content: "attr(data-display)",
5439
+ position: "absolute",
5440
+ pointerEvents: "none",
5441
+ color: "#888"
5442
+ },
5443
+ ".cm-highlightTab": {
5444
+ backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23888" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,
5445
+ backgroundSize: "auto 100%",
5446
+ backgroundPosition: "right 90%",
5447
+ backgroundRepeat: "no-repeat"
5448
+ },
5449
+ ".cm-trailingSpace": {
5450
+ backgroundColor: "#ff332255"
5451
+ },
5434
5452
  ".cm-button": {
5435
5453
  verticalAlign: "middle",
5436
5454
  color: "inherit",
@@ -5733,7 +5751,8 @@ class DOMObserver {
5733
5751
  this.lastChange = 0;
5734
5752
  this.scrollTargets = [];
5735
5753
  this.intersection = null;
5736
- this.resize = null;
5754
+ this.resizeScroll = null;
5755
+ this.resizeContent = null;
5737
5756
  this.intersecting = false;
5738
5757
  this.gapIntersection = null;
5739
5758
  this.gaps = [];
@@ -5771,12 +5790,14 @@ class DOMObserver {
5771
5790
  this.onPrint = this.onPrint.bind(this);
5772
5791
  this.onScroll = this.onScroll.bind(this);
5773
5792
  if (typeof ResizeObserver == "function") {
5774
- this.resize = new ResizeObserver(() => {
5793
+ this.resizeScroll = new ResizeObserver(() => {
5775
5794
  var _a;
5776
5795
  if (((_a = this.view.docView) === null || _a === void 0 ? void 0 : _a.lastUpdate) < Date.now() - 75)
5777
5796
  this.onResize();
5778
5797
  });
5779
- this.resize.observe(view.scrollDOM);
5798
+ this.resizeScroll.observe(view.scrollDOM);
5799
+ this.resizeContent = new ResizeObserver(() => this.view.requestMeasure());
5800
+ this.resizeContent.observe(view.contentDOM);
5780
5801
  }
5781
5802
  this.addWindowListeners(this.win = view.win);
5782
5803
  this.start();
@@ -6095,11 +6116,12 @@ class DOMObserver {
6095
6116
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6096
6117
  }
6097
6118
  destroy() {
6098
- var _a, _b, _c;
6119
+ var _a, _b, _c, _d;
6099
6120
  this.stop();
6100
6121
  (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
6101
6122
  (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
6102
- (_c = this.resize) === null || _c === void 0 ? void 0 : _c.disconnect();
6123
+ (_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
6124
+ (_d = this.resizeContent) === null || _d === void 0 ? void 0 : _d.disconnect();
6103
6125
  for (let dom of this.scrollTargets)
6104
6126
  dom.removeEventListener("scroll", this.onScroll);
6105
6127
  this.removeWindowListeners(this.win);
@@ -6198,7 +6220,7 @@ class EditorView {
6198
6220
  this.scrollDOM.className = "cm-scroller";
6199
6221
  this.scrollDOM.appendChild(this.contentDOM);
6200
6222
  this.announceDOM = document.createElement("div");
6201
- this.announceDOM.style.cssText = "position: absolute; top: -10000px";
6223
+ this.announceDOM.style.cssText = "position: fixed; top: -10000px";
6202
6224
  this.announceDOM.setAttribute("aria-live", "polite");
6203
6225
  this.dom = document.createElement("div");
6204
6226
  this.dom.appendChild(this.announceDOM);
@@ -7289,7 +7311,8 @@ a rectangle at a given set of coordinates.
7289
7311
  */
7290
7312
  class RectangleMarker {
7291
7313
  /**
7292
- Create a marker with the given class and dimensions.
7314
+ Create a marker with the given class and dimensions. If `width`
7315
+ is null, the DOM element will get no width style.
7293
7316
  */
7294
7317
  constructor(className, left, top, width, height) {
7295
7318
  this.className = className;
@@ -7313,7 +7336,7 @@ class RectangleMarker {
7313
7336
  adjust(elt) {
7314
7337
  elt.style.left = this.left + "px";
7315
7338
  elt.style.top = this.top + "px";
7316
- if (this.width >= 0)
7339
+ if (this.width != null)
7317
7340
  elt.style.width = this.width + "px";
7318
7341
  elt.style.height = this.height + "px";
7319
7342
  }
@@ -7321,6 +7344,129 @@ class RectangleMarker {
7321
7344
  return this.left == p.left && this.top == p.top && this.width == p.width && this.height == p.height &&
7322
7345
  this.className == p.className;
7323
7346
  }
7347
+ /**
7348
+ Create a set of rectangles for the given selection range,
7349
+ assigning them theclass`className`. Will create a single
7350
+ rectangle for empty ranges, and a set of selection-style
7351
+ rectangles covering the range's content (in a bidi-aware
7352
+ way) for non-empty ones.
7353
+ */
7354
+ static forRange(view, className, range) {
7355
+ if (range.empty) {
7356
+ let pos = view.coordsAtPos(range.head, range.assoc || 1);
7357
+ if (!pos)
7358
+ return [];
7359
+ let base = getBase(view);
7360
+ return [new RectangleMarker(className, pos.left - base.left, pos.top - base.top, null, pos.bottom - pos.top)];
7361
+ }
7362
+ else {
7363
+ return rectanglesForRange(view, className, range);
7364
+ }
7365
+ }
7366
+ }
7367
+ function getBase(view) {
7368
+ let rect = view.scrollDOM.getBoundingClientRect();
7369
+ let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
7370
+ return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop };
7371
+ }
7372
+ function wrappedLine(view, pos, inside) {
7373
+ let range = EditorSelection.cursor(pos);
7374
+ return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
7375
+ to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
7376
+ type: BlockType.Text };
7377
+ }
7378
+ function blockAt(view, pos) {
7379
+ let line = view.lineBlockAt(pos);
7380
+ if (Array.isArray(line.type))
7381
+ for (let l of line.type) {
7382
+ if (l.to > pos || l.to == pos && (l.to == line.to || l.type == BlockType.Text))
7383
+ return l;
7384
+ }
7385
+ return line;
7386
+ }
7387
+ function rectanglesForRange(view, className, range) {
7388
+ if (range.to <= view.viewport.from || range.from >= view.viewport.to)
7389
+ return [];
7390
+ let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
7391
+ let ltr = view.textDirection == Direction.LTR;
7392
+ let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
7393
+ let lineStyle = window.getComputedStyle(content.firstChild);
7394
+ let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
7395
+ let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
7396
+ let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
7397
+ let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
7398
+ let visualEnd = endBlock.type == BlockType.Text ? endBlock : null;
7399
+ if (view.lineWrapping) {
7400
+ if (visualStart)
7401
+ visualStart = wrappedLine(view, from, visualStart);
7402
+ if (visualEnd)
7403
+ visualEnd = wrappedLine(view, to, visualEnd);
7404
+ }
7405
+ if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
7406
+ return pieces(drawForLine(range.from, range.to, visualStart));
7407
+ }
7408
+ else {
7409
+ let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
7410
+ let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
7411
+ let between = [];
7412
+ if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
7413
+ between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
7414
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
7415
+ top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
7416
+ return pieces(top).concat(between).concat(pieces(bottom));
7417
+ }
7418
+ function piece(left, top, right, bottom) {
7419
+ return new RectangleMarker(className, left - base.left, top - base.top - 0.01 /* C.Epsilon */, right - left, bottom - top + 0.01 /* C.Epsilon */);
7420
+ }
7421
+ function pieces({ top, bottom, horizontal }) {
7422
+ let pieces = [];
7423
+ for (let i = 0; i < horizontal.length; i += 2)
7424
+ pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
7425
+ return pieces;
7426
+ }
7427
+ // Gets passed from/to in line-local positions
7428
+ function drawForLine(from, to, line) {
7429
+ let top = 1e9, bottom = -1e9, horizontal = [];
7430
+ function addSpan(from, fromOpen, to, toOpen, dir) {
7431
+ // Passing 2/-2 is a kludge to force the view to return
7432
+ // coordinates on the proper side of block widgets, since
7433
+ // normalizing the side there, though appropriate for most
7434
+ // coordsAtPos queries, would break selection drawing.
7435
+ let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7436
+ let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7437
+ top = Math.min(fromCoords.top, toCoords.top, top);
7438
+ bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7439
+ if (dir == Direction.LTR)
7440
+ horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
7441
+ else
7442
+ horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
7443
+ }
7444
+ let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
7445
+ // Split the range by visible range and document line
7446
+ for (let r of view.visibleRanges)
7447
+ if (r.to > start && r.from < end) {
7448
+ for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
7449
+ let docLine = view.state.doc.lineAt(pos);
7450
+ for (let span of view.bidiSpans(docLine)) {
7451
+ let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
7452
+ if (spanFrom >= endPos)
7453
+ break;
7454
+ if (spanTo > pos)
7455
+ addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
7456
+ }
7457
+ pos = docLine.to + 1;
7458
+ if (pos >= endPos)
7459
+ break;
7460
+ }
7461
+ }
7462
+ if (horizontal.length == 0)
7463
+ addSpan(start, from == null, end, to == null, view.textDirection);
7464
+ return { top, bottom, horizontal };
7465
+ }
7466
+ function drawForWidget(block, top) {
7467
+ let y = contentRect.top + (top ? block.top : block.bottom);
7468
+ return { top: y, bottom: y, horizontal: [] };
7469
+ }
7324
7470
  }
7325
7471
  function sameMarker(a, b) {
7326
7472
  return a.constructor == b.constructor && a.eq(b);
@@ -7380,6 +7526,8 @@ class LayerView {
7380
7526
  }
7381
7527
  }
7382
7528
  destroy() {
7529
+ if (this.layer.destroy)
7530
+ this.layer.destroy(this.dom, this.view);
7383
7531
  this.dom.remove();
7384
7532
  }
7385
7533
  }
@@ -7444,8 +7592,9 @@ const cursorLayer = /*@__PURE__*/layer({
7444
7592
  for (let r of state.selection.ranges) {
7445
7593
  let prim = r == state.selection.main;
7446
7594
  if (r.empty ? !prim || CanHidePrimary : conf.drawRangeCursor) {
7447
- let piece = measureCursor(view, r, prim);
7448
- if (piece)
7595
+ let className = prim ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary";
7596
+ let cursor = r.empty ? r : EditorSelection.cursor(r.head, r.head > r.anchor ? -1 : 1);
7597
+ for (let piece of RectangleMarker.forRange(view, className, cursor))
7449
7598
  cursors.push(piece);
7450
7599
  }
7451
7600
  }
@@ -7470,7 +7619,8 @@ function setBlinkRate(state, dom) {
7470
7619
  const selectionLayer = /*@__PURE__*/layer({
7471
7620
  above: false,
7472
7621
  markers(view) {
7473
- return view.state.selection.ranges.map(r => r.empty ? [] : measureRange(view, r)).reduce((a, b) => a.concat(b));
7622
+ return view.state.selection.ranges.map(r => r.empty ? [] : RectangleMarker.forRange(view, "cm-selectionBackground", r))
7623
+ .reduce((a, b) => a.concat(b));
7474
7624
  },
7475
7625
  update(update, dom) {
7476
7626
  return update.docChanged || update.selectionSet || update.viewportChanged || configChanged(update);
@@ -7486,117 +7636,6 @@ const themeSpec = {
7486
7636
  if (CanHidePrimary)
7487
7637
  themeSpec[".cm-line"].caretColor = "transparent !important";
7488
7638
  const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme(themeSpec));
7489
- function getBase(view) {
7490
- let rect = view.scrollDOM.getBoundingClientRect();
7491
- let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
7492
- return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop };
7493
- }
7494
- function wrappedLine(view, pos, inside) {
7495
- let range = EditorSelection.cursor(pos);
7496
- return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
7497
- to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
7498
- type: BlockType.Text };
7499
- }
7500
- function blockAt(view, pos) {
7501
- let line = view.lineBlockAt(pos);
7502
- if (Array.isArray(line.type))
7503
- for (let l of line.type) {
7504
- if (l.to > pos || l.to == pos && (l.to == line.to || l.type == BlockType.Text))
7505
- return l;
7506
- }
7507
- return line;
7508
- }
7509
- function measureRange(view, range) {
7510
- if (range.to <= view.viewport.from || range.from >= view.viewport.to)
7511
- return [];
7512
- let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
7513
- let ltr = view.textDirection == Direction.LTR;
7514
- let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
7515
- let lineStyle = window.getComputedStyle(content.firstChild);
7516
- let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
7517
- let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
7518
- let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
7519
- let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
7520
- let visualEnd = endBlock.type == BlockType.Text ? endBlock : null;
7521
- if (view.lineWrapping) {
7522
- if (visualStart)
7523
- visualStart = wrappedLine(view, from, visualStart);
7524
- if (visualEnd)
7525
- visualEnd = wrappedLine(view, to, visualEnd);
7526
- }
7527
- if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
7528
- return pieces(drawForLine(range.from, range.to, visualStart));
7529
- }
7530
- else {
7531
- let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
7532
- let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
7533
- let between = [];
7534
- if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
7535
- between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
7536
- else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
7537
- top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
7538
- return pieces(top).concat(between).concat(pieces(bottom));
7539
- }
7540
- function piece(left, top, right, bottom) {
7541
- return new RectangleMarker("cm-selectionBackground", left - base.left, top - base.top - 0.01 /* C.Epsilon */, right - left, bottom - top + 0.01 /* C.Epsilon */);
7542
- }
7543
- function pieces({ top, bottom, horizontal }) {
7544
- let pieces = [];
7545
- for (let i = 0; i < horizontal.length; i += 2)
7546
- pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
7547
- return pieces;
7548
- }
7549
- // Gets passed from/to in line-local positions
7550
- function drawForLine(from, to, line) {
7551
- let top = 1e9, bottom = -1e9, horizontal = [];
7552
- function addSpan(from, fromOpen, to, toOpen, dir) {
7553
- // Passing 2/-2 is a kludge to force the view to return
7554
- // coordinates on the proper side of block widgets, since
7555
- // normalizing the side there, though appropriate for most
7556
- // coordsAtPos queries, would break selection drawing.
7557
- let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7558
- let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7559
- top = Math.min(fromCoords.top, toCoords.top, top);
7560
- bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7561
- if (dir == Direction.LTR)
7562
- horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
7563
- else
7564
- horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
7565
- }
7566
- let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
7567
- // Split the range by visible range and document line
7568
- for (let r of view.visibleRanges)
7569
- if (r.to > start && r.from < end) {
7570
- for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
7571
- let docLine = view.state.doc.lineAt(pos);
7572
- for (let span of view.bidiSpans(docLine)) {
7573
- let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
7574
- if (spanFrom >= endPos)
7575
- break;
7576
- if (spanTo > pos)
7577
- addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
7578
- }
7579
- pos = docLine.to + 1;
7580
- if (pos >= endPos)
7581
- break;
7582
- }
7583
- }
7584
- if (horizontal.length == 0)
7585
- addSpan(start, from == null, end, to == null, view.textDirection);
7586
- return { top, bottom, horizontal };
7587
- }
7588
- function drawForWidget(block, top) {
7589
- let y = contentRect.top + (top ? block.top : block.bottom);
7590
- return { top: y, bottom: y, horizontal: [] };
7591
- }
7592
- }
7593
- function measureCursor(view, cursor, primary) {
7594
- let pos = view.coordsAtPos(cursor.head, cursor.assoc || 1);
7595
- if (!pos)
7596
- return null;
7597
- let base = getBase(view);
7598
- return new RectangleMarker(primary ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary", pos.left - base.left, pos.top - base.top, -1, pos.bottom - pos.top);
7599
- }
7600
7639
 
7601
7640
  const setDropCursorPos = /*@__PURE__*/StateEffect.define({
7602
7641
  map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
@@ -9363,9 +9402,60 @@ function highlightActiveLineGutter() {
9363
9402
  return activeLineGutterHighlighter;
9364
9403
  }
9365
9404
 
9405
+ const WhitespaceDeco = /*@__PURE__*/new Map();
9406
+ function getWhitespaceDeco(space) {
9407
+ let deco = WhitespaceDeco.get(space);
9408
+ if (!deco)
9409
+ WhitespaceDeco.set(space, deco = Decoration.mark({
9410
+ attributes: space === "\t" ? {
9411
+ class: "cm-highlightTab",
9412
+ } : {
9413
+ class: "cm-highlightSpace",
9414
+ "data-display": space.replace(/ /g, "·")
9415
+ }
9416
+ }));
9417
+ return deco;
9418
+ }
9419
+ function matcher(decorator) {
9420
+ return ViewPlugin.define(view => ({
9421
+ decorations: decorator.createDeco(view),
9422
+ update(u) {
9423
+ this.decorations = decorator.updateDeco(u, this.decorations);
9424
+ },
9425
+ }), {
9426
+ decorations: v => v.decorations
9427
+ });
9428
+ }
9429
+ const whitespaceHighlighter = /*@__PURE__*/matcher(/*@__PURE__*/new MatchDecorator({
9430
+ regexp: /\t| +/g,
9431
+ decoration: match => getWhitespaceDeco(match[0]),
9432
+ boundary: /\S/,
9433
+ }));
9434
+ /**
9435
+ Returns an extension that highlights whitespace, adding a
9436
+ `cm-highlightSpace` class to stretches of spaces, and a
9437
+ `cm-highlightTab` class to individual tab characters. By default,
9438
+ the former are shown as faint dots, and the latter as arrows.
9439
+ */
9440
+ function highlightWhitespace() {
9441
+ return whitespaceHighlighter;
9442
+ }
9443
+ const trailingHighlighter = /*@__PURE__*/matcher(/*@__PURE__*/new MatchDecorator({
9444
+ regexp: /\s+$/g,
9445
+ decoration: /*@__PURE__*/Decoration.mark({ class: "cm-trailingSpace" }),
9446
+ boundary: /\S/,
9447
+ }));
9448
+ /**
9449
+ Returns an extension that adds a `cm-trailingSpace` class to all
9450
+ trailing whitespace.
9451
+ */
9452
+ function highlightTrailingWhitespace() {
9453
+ return trailingHighlighter;
9454
+ }
9455
+
9366
9456
  /**
9367
9457
  @internal
9368
9458
  */
9369
9459
  const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder, moveVisually };
9370
9460
 
9371
- export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, GutterMarker, MatchDecorator, RectangleMarker, ViewPlugin, ViewUpdate, WidgetType, __test, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getPanel, getTooltip, gutter, gutterLineClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showPanel, showTooltip, tooltips };
9461
+ export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, GutterMarker, MatchDecorator, RectangleMarker, ViewPlugin, ViewUpdate, WidgetType, __test, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getPanel, getTooltip, gutter, gutterLineClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showPanel, showTooltip, tooltips };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.6.0",
3
+ "version": "6.7.0",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",