@batchfy/codemirror-minimap 0.1.0 → 0.1.1

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
@@ -1,8 +1,8 @@
1
1
  import { Facet, combineConfig, StateField } from '@codemirror/state';
2
2
  import { EditorView, ViewPlugin } from '@codemirror/view';
3
+ import { foldEffect, unfoldEffect, foldedRanges, language, highlightingFor } from '@codemirror/language';
3
4
  import crelt from 'crelt';
4
5
  import { setDiagnosticsEffect, diagnosticCount, forEachDiagnostic } from '@codemirror/lint';
5
- import { foldEffect, unfoldEffect, foldedRanges, language, highlightingFor } from '@codemirror/language';
6
6
  import { highlightTree } from '@lezer/highlight';
7
7
  import { TreeFragment } from '@lezer/common';
8
8
 
@@ -41,12 +41,224 @@ const Config = Facet.define({
41
41
  }
42
42
  });
43
43
 
44
+ function computeLinesState(state) {
45
+ if (!state.facet(Config).enabled) {
46
+ return [];
47
+ }
48
+ const lines = [];
49
+ const lineCursor = state.doc.iterLines();
50
+ const foldedRangeCursor = foldedRanges(state).iter();
51
+ let textOffset = 0;
52
+ lineCursor.next();
53
+ while (!lineCursor.done) {
54
+ const lineText = lineCursor.value;
55
+ const from = textOffset;
56
+ const to = from + lineText.length;
57
+ while (foldedRangeCursor.value && foldedRangeCursor.to < from) {
58
+ foldedRangeCursor.next();
59
+ }
60
+ const { from: foldFrom, to: foldTo } = foldedRangeCursor;
61
+ const lineStartInFold = from >= foldFrom && from < foldTo;
62
+ const lineEndsInFold = to > foldFrom && to <= foldTo;
63
+ if (lineStartInFold) {
64
+ const lastLine = lines.pop() ?? [];
65
+ const lastRange = lastLine.pop();
66
+ if (lastRange && lastRange.folded) {
67
+ lastRange.to = foldTo;
68
+ }
69
+ if (lastRange) {
70
+ lastLine.push(lastRange);
71
+ }
72
+ if (!lastRange || !lastRange.folded) {
73
+ lastLine.push({ from: foldFrom, to: foldTo, folded: true });
74
+ }
75
+ if (!lineEndsInFold) {
76
+ lastLine.push({ from: foldTo, to, folded: false });
77
+ }
78
+ lines.push(lastLine);
79
+ } else if (lineEndsInFold) {
80
+ lines.push([
81
+ { from, to: foldFrom, folded: false },
82
+ { from: foldFrom, to: foldTo, folded: true }
83
+ ]);
84
+ } else {
85
+ lines.push([{ from, to, folded: false }]);
86
+ }
87
+ textOffset = to + 1;
88
+ lineCursor.next();
89
+ }
90
+ return lines;
91
+ }
92
+ const LinesState = StateField.define({
93
+ create: (state) => computeLinesState(state),
94
+ update: (current, tr) => {
95
+ if (foldsChanged([tr]) || tr.docChanged) {
96
+ return computeLinesState(tr.state);
97
+ }
98
+ return current;
99
+ }
100
+ });
101
+ function foldsChanged(transactions) {
102
+ return transactions.some(
103
+ (tr) => tr.effects.some((ef) => ef.is(foldEffect) || ef.is(unfoldEffect))
104
+ );
105
+ }
106
+
107
+ const SCALE = Scale.PixelMultiplier * Scale.SizeRatio;
108
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
109
+ function computeLayout(input) {
110
+ const { rowCount, lineHeight, paddingTop, paddingBottom, height } = input;
111
+ const lastRow = Math.max(0, rowCount - 1);
112
+ const first = clamp(input.firstVisibleRow, 0, lastRow);
113
+ const last = clamp(input.lastVisibleRow, first, lastRow);
114
+ const visibleRows2 = last - first + 1;
115
+ const contentHeight = paddingTop + paddingBottom + rowCount * lineHeight;
116
+ const overlayHeight = Math.min(height, visibleRows2 * lineHeight);
117
+ const rowTop = paddingTop + first * lineHeight;
118
+ const scrollableRows = Math.max(0, rowCount - input.rowsPerScreen);
119
+ const progress = scrollableRows <= 0 ? 0 : clamp(first / scrollableRows, 0, 1);
120
+ const maxScroll = Math.max(0, contentHeight - height);
121
+ const scrollTop = clamp(
122
+ maxScroll * progress,
123
+ Math.max(0, rowTop - (height - overlayHeight)),
124
+ Math.min(maxScroll, rowTop)
125
+ );
126
+ const overlayTravel = Math.max(
127
+ 0,
128
+ Math.min(height, contentHeight) - paddingTop - paddingBottom - input.rowsPerScreen * lineHeight
129
+ );
130
+ return {
131
+ contentHeight,
132
+ scrollTop,
133
+ overlayTop: rowTop - scrollTop,
134
+ overlayHeight,
135
+ rowCount,
136
+ scrollableRows,
137
+ overlayTravel
138
+ };
139
+ }
140
+ const rowHeights = /* @__PURE__ */ new WeakMap();
141
+ function setRowHeight(view, canvasLineHeight) {
142
+ const lineHeight = canvasLineHeight / Scale.PixelMultiplier;
143
+ if (rowHeights.get(view) !== lineHeight) {
144
+ rowHeights.set(view, lineHeight);
145
+ measured.delete(view);
146
+ }
147
+ }
148
+ function rowAtPos(lines, pos) {
149
+ let low = 0;
150
+ let high = lines.length - 1;
151
+ while (low < high) {
152
+ const mid = Math.ceil((low + high) / 2);
153
+ const line = lines[mid];
154
+ if (line && line.length && line[0].from <= pos) {
155
+ low = mid;
156
+ } else {
157
+ high = mid - 1;
158
+ }
159
+ }
160
+ return Math.max(0, low);
161
+ }
162
+ function lineBlockAtHeight(view, height) {
163
+ const { doc } = view.state;
164
+ let low = 1;
165
+ let high = doc.lines;
166
+ while (low < high) {
167
+ const mid = Math.floor((low + high) / 2);
168
+ if (view.lineBlockAt(doc.line(mid).from).bottom <= height) {
169
+ low = mid + 1;
170
+ } else {
171
+ high = mid;
172
+ }
173
+ }
174
+ return view.lineBlockAt(doc.line(low).from);
175
+ }
176
+ function visibleRows(view) {
177
+ const lines = view.state.field(LinesState);
178
+ const { top, bottom } = view.scrollDOM.getBoundingClientRect();
179
+ const documentTop = view.documentTop;
180
+ return {
181
+ first: rowAtPos(lines, lineBlockAtHeight(view, top - documentTop).from),
182
+ last: rowAtPos(lines, lineBlockAtHeight(view, bottom - documentTop).to)
183
+ };
184
+ }
185
+ const measured = /* @__PURE__ */ new WeakMap();
186
+ function readGeometry(view, token) {
187
+ if (token) {
188
+ const previous = measured.get(view);
189
+ if (previous && previous.token === token) {
190
+ return previous.geometry;
191
+ }
192
+ }
193
+ const geometry = measureGeometry(view);
194
+ if (token) {
195
+ measured.set(view, { token, geometry });
196
+ }
197
+ return geometry;
198
+ }
199
+ function measureGeometry(view) {
200
+ const lineHeight = rowHeights.get(view) || view.defaultLineHeight / SCALE || 0;
201
+ const paddingTop = view.documentPadding.top / SCALE;
202
+ const paddingBottom = view.documentPadding.bottom / SCALE;
203
+ const height = view.dom.getBoundingClientRect().height;
204
+ const { first, last } = visibleRows(view);
205
+ const layout = computeLayout({
206
+ rowCount: view.state.field(LinesState).length,
207
+ lineHeight,
208
+ paddingTop,
209
+ paddingBottom,
210
+ height,
211
+ firstVisibleRow: first,
212
+ lastVisibleRow: last,
213
+ rowsPerScreen: view.defaultLineHeight ? view.scrollDOM.clientHeight / view.defaultLineHeight : 0
214
+ });
215
+ return { ...layout, lineHeight, paddingTop, paddingBottom, height };
216
+ }
217
+ function rowAtY(geometry, y) {
218
+ if (geometry.lineHeight <= 0) {
219
+ return 0;
220
+ }
221
+ const offset = geometry.scrollTop + y - geometry.paddingTop;
222
+ return Math.floor(offset / geometry.lineHeight);
223
+ }
224
+ function rowAtOverlayTop(geometry, overlayTop) {
225
+ if (geometry.overlayTravel <= 0 || geometry.scrollableRows <= 0 || geometry.lineHeight <= 0) {
226
+ return 0;
227
+ }
228
+ const offset = overlayTop - geometry.paddingTop;
229
+ if (offset >= geometry.overlayTravel) {
230
+ return clamp(
231
+ geometry.scrollableRows + (offset - geometry.overlayTravel) / geometry.lineHeight,
232
+ 0,
233
+ Math.max(0, geometry.rowCount - 1)
234
+ );
235
+ }
236
+ const progress = clamp(offset / geometry.overlayTravel, 0, 1);
237
+ return progress * geometry.scrollableRows;
238
+ }
239
+ function scrollToRow(view, row, offset = 0) {
240
+ const lines = view.state.field(LinesState);
241
+ const wanted = clamp(row, 0, lines.length - 1);
242
+ const index = Math.floor(wanted);
243
+ const line = lines[index];
244
+ if (!line || !line.length) {
245
+ return;
246
+ }
247
+ const block = view.lineBlockAt(line[0].from);
248
+ const withinRow = (wanted - index) * block.height;
249
+ view.scrollDOM.scrollTop = view.documentPadding.top + block.top + withinRow - offset;
250
+ }
251
+
44
252
  const Theme$1 = EditorView.theme({
45
253
  ".cm-minimap-overlay-container": {
46
254
  position: "absolute",
47
255
  top: 0,
48
256
  height: "100%",
49
257
  width: "100%",
258
+ // The minimap is a sibling of `.cm-content` inside the scroller, so
259
+ // without this it inherits the caret the editor shows over text. It is
260
+ // not text: clicking it jumps, and the overlay is dragged.
261
+ cursor: "default",
50
262
  "&.cm-minimap-overlay-mouse-over": {
51
263
  opacity: 0,
52
264
  transition: "visibility 0s linear 300ms, opacity 300ms"
@@ -66,6 +278,7 @@ const Theme$1 = EditorView.theme({
66
278
  top: 0,
67
279
  width: "100%",
68
280
  transition: "top 0s ease-in 0ms",
281
+ cursor: "grab",
69
282
  "&:hover": {
70
283
  opacity: "0.3"
71
284
  }
@@ -75,12 +288,12 @@ const Theme$1 = EditorView.theme({
75
288
  visibility: "visible",
76
289
  transition: "visibility 0s linear 0ms, opacity 300ms",
77
290
  "& .cm-minimap-overlay": {
78
- opacity: "0.4"
291
+ opacity: "0.4",
292
+ cursor: "grabbing"
79
293
  }
80
294
  }
81
295
  }
82
296
  });
83
- const SCALE = Scale.PixelMultiplier * Scale.SizeRatio;
84
297
  const OverlayView = ViewPlugin.fromClass(
85
298
  class {
86
299
  constructor(view) {
@@ -93,7 +306,29 @@ const OverlayView = ViewPlugin.fromClass(
93
306
  container;
94
307
  dom;
95
308
  _isDragging = false;
96
- _dragStartY;
309
+ /**
310
+ * Where inside the overlay the drag took hold of it, as a fraction of
311
+ * its height rather than a count of pixels down from its top.
312
+ *
313
+ * The overlay is sized to the lines on screen, so it shrinks as a drag
314
+ * carries it from a screen of code into one of wrapped prose. A pixel
315
+ * offset taken when it was tall goes on pointing at where its middle
316
+ * used to be, which by the end of the document is somewhere below its
317
+ * bottom edge — and the pointer would have to leave the minimap, often
318
+ * the window, to put the overlay where it belongs. A fraction shrinks
319
+ * with it and stays under the thumb.
320
+ */
321
+ _dragAnchor;
322
+ /** The last position the pointer reported, waiting for a frame. */
323
+ _dragPointerY;
324
+ _dragFrame;
325
+ /**
326
+ * Measured when the drag starts and kept for its duration. None of it
327
+ * moves as the document scrolls, and `update` drops it so that anything
328
+ * which does move it — an edit, a resize, a change of theme — is
329
+ * measured again.
330
+ */
331
+ _dragGeometry;
97
332
  // Bound once so that removeEventListener sees the same reference that
98
333
  // addEventListener was given; `.bind` at call time yields a new
99
334
  // function each call and the listener would never be detached.
@@ -114,10 +349,12 @@ const OverlayView = ViewPlugin.fromClass(
114
349
  inner.appendChild(this.container);
115
350
  }
116
351
  this.computeShowOverlay();
117
- this.computeHeight();
118
- this.computeTop();
352
+ this.position();
119
353
  }
120
354
  remove() {
355
+ if (this._isDragging) {
356
+ this.endDrag();
357
+ }
121
358
  if (this.container) {
122
359
  this.container.removeEventListener(
123
360
  "mousedown",
@@ -139,33 +376,26 @@ const OverlayView = ViewPlugin.fromClass(
139
376
  this.create(update.view);
140
377
  }
141
378
  if (now) {
379
+ this._dragGeometry = void 0;
142
380
  this.computeShowOverlay();
143
- if (update.geometryChanged) {
144
- this.computeHeight();
145
- this.computeTop();
146
- }
381
+ this.position(update);
147
382
  }
148
383
  }
149
- computeHeight() {
384
+ /**
385
+ * Covers the rows the editor is showing. The renderer scrolls its rows
386
+ * to the position the same geometry prescribes, so the overlay lands on
387
+ * the drawn content whatever the document does with wrapping or folds.
388
+ */
389
+ position(token) {
150
390
  if (!this.dom) {
151
391
  return;
152
392
  }
153
- const height = this.view.dom.clientHeight / SCALE;
154
- this.dom.style.height = height + "px";
155
- }
156
- computeTop() {
157
- if (!this._isDragging && this.dom) {
158
- const { clientHeight, scrollHeight, scrollTop } = this.view.scrollDOM;
159
- const maxScrollTop = scrollHeight - clientHeight;
160
- const topForNonOverflowing = scrollTop / SCALE;
161
- const height = clientHeight / SCALE;
162
- const maxTop = clientHeight - height;
163
- let scrollRatio = scrollTop / maxScrollTop;
164
- if (isNaN(scrollRatio)) scrollRatio = 0;
165
- const topForOverflowing = maxTop * scrollRatio;
166
- const top = Math.min(topForOverflowing, topForNonOverflowing);
167
- this.dom.style.top = top + "px";
168
- }
393
+ const { overlayTop, overlayHeight } = readGeometry(
394
+ this.view,
395
+ token
396
+ );
397
+ this.dom.style.top = overlayTop + "px";
398
+ this.dom.style.height = overlayHeight + "px";
169
399
  }
170
400
  computeShowOverlay() {
171
401
  if (!this.container) {
@@ -193,75 +423,64 @@ const OverlayView = ViewPlugin.fromClass(
193
423
  if (event.button === 2) {
194
424
  return;
195
425
  }
426
+ event.preventDefault();
196
427
  const { clientY, target } = event;
197
- if (target === this.dom) {
198
- this._dragStartY = event.clientY;
428
+ if (target === this.dom && this.dom) {
429
+ const grabbed = this.dom.getBoundingClientRect();
430
+ this._dragAnchor = grabbed.height > 0 ? (clientY - grabbed.top) / grabbed.height : 0;
199
431
  this._isDragging = true;
200
432
  this.container.classList.add("cm-minimap-overlay-active");
433
+ document.body.style.cursor = "grabbing";
201
434
  return;
202
435
  }
203
- const { clientHeight, scrollHeight, scrollTop } = this.view.scrollDOM;
204
- const targetTop = target.getBoundingClientRect().top;
205
- const deltaY = (clientY - targetTop) * SCALE;
206
- const scrollRatio = scrollTop / (scrollHeight - clientHeight);
207
- const visibleRange = clientHeight * SCALE - clientHeight;
208
- const visibleTop = visibleRange * scrollRatio;
209
- const top = Math.max(0, scrollTop - visibleTop);
210
- this.view.scrollDOM.scrollTop = top + deltaY - clientHeight / 2;
436
+ const geometry = readGeometry(this.view);
437
+ const y = clientY - this.container.getBoundingClientRect().top;
438
+ scrollToRow(
439
+ this.view,
440
+ rowAtY(geometry, y),
441
+ this.view.scrollDOM.clientHeight / 2
442
+ );
211
443
  }
212
444
  onMouseUp(_event) {
213
- if (this._isDragging && this.container) {
214
- this._dragStartY = void 0;
215
- this._isDragging = false;
216
- this.container.classList.remove("cm-minimap-overlay-active");
445
+ if (this._isDragging) {
446
+ this.endDrag();
217
447
  }
218
448
  }
449
+ endDrag() {
450
+ if (this._dragFrame !== void 0) {
451
+ cancelAnimationFrame(this._dragFrame);
452
+ this._dragFrame = void 0;
453
+ }
454
+ this._dragAnchor = void 0;
455
+ this._dragPointerY = void 0;
456
+ this._dragGeometry = void 0;
457
+ this._isDragging = false;
458
+ this.container?.classList.remove("cm-minimap-overlay-active");
459
+ document.body.style.removeProperty("cursor");
460
+ }
219
461
  onMouseMove(event) {
220
- if (!this._isDragging || !this.dom) {
462
+ if (!this._isDragging || this._dragAnchor === void 0) {
221
463
  return;
222
464
  }
223
465
  event.preventDefault();
224
466
  event.stopPropagation();
225
- if (!this._dragStartY) {
226
- this._dragStartY = event.clientY;
227
- return;
228
- }
229
- const deltaY = event.clientY - this._dragStartY;
230
- const movingUp = deltaY < 0;
231
- const movingDown = deltaY > 0;
232
- this._dragStartY = event.clientY;
233
- const canvasHeight = this.dom.getBoundingClientRect().height;
234
- const canvasAbsTop = this.dom.getBoundingClientRect().y;
235
- const canvasAbsBot = canvasAbsTop + canvasHeight;
236
- const canvasRelTopDouble = parseFloat(this.dom.style.top);
237
- const scrollPosition = this.view.scrollDOM.scrollTop;
238
- const editorHeight = this.view.scrollDOM.clientHeight;
239
- const contentHeight = this.view.scrollDOM.scrollHeight;
240
- const atTop = scrollPosition === 0;
241
- const atBottom = Math.round(scrollPosition) >= Math.round(contentHeight - editorHeight);
242
- if (atTop && movingUp || atTop && event.clientY < canvasAbsTop) {
243
- return;
467
+ this._dragPointerY = event.clientY;
468
+ if (this._dragFrame === void 0) {
469
+ this._dragFrame = requestAnimationFrame(() => {
470
+ this._dragFrame = void 0;
471
+ this.dragToPointer();
472
+ });
244
473
  }
245
- if (atBottom && movingDown || atBottom && event.clientY > canvasAbsBot) {
474
+ }
475
+ dragToPointer() {
476
+ if (!this._isDragging || this._dragAnchor === void 0 || this._dragPointerY === void 0 || !this.container || !this.dom) {
246
477
  return;
247
478
  }
248
- const scrollHeight = this.view.scrollDOM.scrollHeight;
249
- const clientHeight = this.view.scrollDOM.clientHeight;
250
- const maxTopNonOverflowing = (scrollHeight - clientHeight) / SCALE;
251
- const maxTopOverflowing = clientHeight - clientHeight / SCALE;
252
- const change = canvasRelTopDouble + deltaY;
253
- const relativeToMax = change / maxTopOverflowing;
254
- const scrollPosOverflowing = (scrollHeight - clientHeight) * relativeToMax;
255
- const scrollPosNonOverflowing = change * SCALE;
256
- this.view.scrollDOM.scrollTop = Math.max(
257
- scrollPosOverflowing,
258
- scrollPosNonOverflowing
259
- );
260
- const top = Math.min(
261
- Math.max(0, change),
262
- Math.min(maxTopOverflowing, maxTopNonOverflowing)
263
- );
264
- this.dom.style.top = top + "px";
479
+ this._dragGeometry ??= readGeometry(this.view);
480
+ const overlayHeight = this.dom.getBoundingClientRect().height;
481
+ const containerTop = this.container.getBoundingClientRect().top;
482
+ const top = this._dragPointerY - this._dragAnchor * overlayHeight - containerTop;
483
+ scrollToRow(this.view, rowAtOverlayTop(this._dragGeometry, top));
265
484
  }
266
485
  destroy() {
267
486
  this.remove();
@@ -269,8 +488,8 @@ const OverlayView = ViewPlugin.fromClass(
269
488
  },
270
489
  {
271
490
  eventHandlers: {
272
- scroll() {
273
- requestAnimationFrame(() => this.computeTop());
491
+ scroll(event) {
492
+ requestAnimationFrame(() => this.position(event));
274
493
  }
275
494
  }
276
495
  }
@@ -292,69 +511,6 @@ class LineBasedState {
292
511
  }
293
512
  }
294
513
 
295
- function computeLinesState(state) {
296
- if (!state.facet(Config).enabled) {
297
- return [];
298
- }
299
- const lines = [];
300
- const lineCursor = state.doc.iterLines();
301
- const foldedRangeCursor = foldedRanges(state).iter();
302
- let textOffset = 0;
303
- lineCursor.next();
304
- while (!lineCursor.done) {
305
- const lineText = lineCursor.value;
306
- const from = textOffset;
307
- const to = from + lineText.length;
308
- while (foldedRangeCursor.value && foldedRangeCursor.to < from) {
309
- foldedRangeCursor.next();
310
- }
311
- const { from: foldFrom, to: foldTo } = foldedRangeCursor;
312
- const lineStartInFold = from >= foldFrom && from < foldTo;
313
- const lineEndsInFold = to > foldFrom && to <= foldTo;
314
- if (lineStartInFold) {
315
- const lastLine = lines.pop() ?? [];
316
- const lastRange = lastLine.pop();
317
- if (lastRange && lastRange.folded) {
318
- lastRange.to = foldTo;
319
- }
320
- if (lastRange) {
321
- lastLine.push(lastRange);
322
- }
323
- if (!lastRange || !lastRange.folded) {
324
- lastLine.push({ from: foldFrom, to: foldTo, folded: true });
325
- }
326
- if (!lineEndsInFold) {
327
- lastLine.push({ from: foldTo, to, folded: false });
328
- }
329
- lines.push(lastLine);
330
- } else if (lineEndsInFold) {
331
- lines.push([
332
- { from, to: foldFrom, folded: false },
333
- { from: foldFrom, to: foldTo, folded: true }
334
- ]);
335
- } else {
336
- lines.push([{ from, to, folded: false }]);
337
- }
338
- textOffset = to + 1;
339
- lineCursor.next();
340
- }
341
- return lines;
342
- }
343
- const LinesState = StateField.define({
344
- create: (state) => computeLinesState(state),
345
- update: (current, tr) => {
346
- if (foldsChanged([tr]) || tr.docChanged) {
347
- return computeLinesState(tr.state);
348
- }
349
- return current;
350
- }
351
- });
352
- function foldsChanged(transactions) {
353
- return transactions.some(
354
- (tr) => tr.effects.some((ef) => ef.is(foldEffect) || ef.is(unfoldEffect))
355
- );
356
- }
357
-
358
514
  class DiagnosticState extends LineBasedState {
359
515
  count = void 0;
360
516
  constructor(view) {
@@ -1009,7 +1165,7 @@ const minimapClass = ViewPlugin.fromClass(
1009
1165
  this.text.update(update);
1010
1166
  this.selection.update(update);
1011
1167
  this.diagnostic.update(update);
1012
- this.render();
1168
+ this.render(update);
1013
1169
  }
1014
1170
  }
1015
1171
  getWidth() {
@@ -1020,7 +1176,12 @@ const minimapClass = ViewPlugin.fromClass(
1020
1176
  }
1021
1177
  return maxWidth;
1022
1178
  }
1023
- render() {
1179
+ /**
1180
+ * `token` names the moment being drawn, and is passed on to
1181
+ * `readGeometry` so that the overlay, which draws the same moment,
1182
+ * measures it only once between them.
1183
+ */
1184
+ render(token) {
1024
1185
  if (!this.dom || !this.canvas || !this.inner) {
1025
1186
  return;
1026
1187
  }
@@ -1051,9 +1212,11 @@ const minimapClass = ViewPlugin.fromClass(
1051
1212
  }
1052
1213
  context.globalAlpha = 1;
1053
1214
  const { charWidth, lineHeight } = this.text.measure(context);
1215
+ setRowHeight(this.view, lineHeight);
1054
1216
  let { startIndex, endIndex, offsetY } = this.canvasStartAndEndIndex(
1055
1217
  context,
1056
- lineHeight
1218
+ lineHeight,
1219
+ token
1057
1220
  );
1058
1221
  const gutters = this.view.state.facet(Config).gutters;
1059
1222
  const lineCount = this.view.state.field(LinesState).length;
@@ -1081,27 +1244,19 @@ const minimapClass = ViewPlugin.fromClass(
1081
1244
  }
1082
1245
  context.restore();
1083
1246
  }
1084
- canvasStartAndEndIndex(context, lineHeight) {
1085
- const { top: rawTop, bottom: rawBottom } = this.view.documentPadding;
1086
- const pTop = rawTop / Scale.SizeRatio;
1087
- const pBottom = rawBottom / Scale.SizeRatio;
1247
+ canvasStartAndEndIndex(context, lineHeight, token) {
1248
+ const pTop = this.view.documentPadding.top / Scale.SizeRatio;
1088
1249
  const canvasHeight = context.canvas.height;
1089
- const { clientHeight, scrollHeight, scrollTop } = this.view.scrollDOM;
1090
- let scrollPercent = scrollTop / (scrollHeight - clientHeight);
1091
- if (isNaN(scrollPercent)) {
1092
- scrollPercent = 0;
1250
+ const canvasTop = readGeometry(this.view, token).scrollTop * Scale.PixelMultiplier;
1251
+ if (lineHeight <= 0) {
1252
+ return { startIndex: 0, endIndex: 0, offsetY: 0 };
1093
1253
  }
1094
- const lineCount = this.view.state.field(LinesState).length;
1095
- const totalHeight = pTop + pBottom + lineCount * lineHeight;
1096
- const canvasTop = Math.max(
1254
+ const startIndex = Math.max(
1097
1255
  0,
1098
- scrollPercent * (totalHeight - canvasHeight)
1099
- );
1100
- const offsetY = Math.max(0, pTop - canvasTop);
1101
- const startIndex = Math.round(
1102
- Math.max(0, canvasTop - pTop) / lineHeight
1256
+ Math.floor((canvasTop - pTop) / lineHeight)
1103
1257
  );
1104
- const spaceForLines = Math.round(
1258
+ const offsetY = pTop + startIndex * lineHeight - canvasTop;
1259
+ const spaceForLines = Math.ceil(
1105
1260
  (canvasHeight - offsetY) / lineHeight
1106
1261
  );
1107
1262
  return {
@@ -1128,8 +1283,8 @@ const minimapClass = ViewPlugin.fromClass(
1128
1283
  },
1129
1284
  {
1130
1285
  eventHandlers: {
1131
- scroll() {
1132
- requestAnimationFrame(() => this.render());
1286
+ scroll(event) {
1287
+ requestAnimationFrame(() => this.render(event));
1133
1288
  }
1134
1289
  },
1135
1290
  provide: (plugin) => {