@adecore/editor 0.39.0 → 0.40.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/dist/controller.d.ts +1 -0
- package/dist/controller.js +17 -28
- package/dist/engine.js +2 -9
- package/dist/find.js +12 -13
- package/dist/layout.js +7 -2
- package/dist/paint.d.ts +2 -0
- package/dist/paint.js +23 -20
- package/dist/view.d.ts +3 -2
- package/dist/view.js +46 -59
- package/package.json +6 -6
- package/src/attribution.ts +14 -0
- package/src/code-block.ts +25 -0
- package/src/column-selection.ts +10 -0
- package/src/controller.ts +189 -28
- package/src/engine.ts +87 -9
- package/src/fake.ts +79 -0
- package/src/find-search.ts +20 -0
- package/src/find.ts +135 -14
- package/src/geometry-stops.ts +10 -0
- package/src/grammar-preparation.ts +23 -0
- package/src/index.ts +1 -0
- package/src/input.ts +13 -0
- package/src/keymap-table.ts +9 -0
- package/src/keymap.ts +25 -0
- package/src/layout-changes.ts +7 -0
- package/src/layout.ts +219 -3
- package/src/lens.ts +3 -0
- package/src/listeners.ts +1 -0
- package/src/metrics.ts +14 -0
- package/src/modifier-gesture.ts +17 -0
- package/src/native-input.ts +27 -0
- package/src/offsets.ts +10 -0
- package/src/outline.ts +38 -0
- package/src/overview.ts +13 -0
- package/src/paint.ts +140 -20
- package/src/php-html.ts +1 -0
- package/src/pointer.ts +50 -0
- package/src/scroll-animation.ts +10 -0
- package/src/scroll.ts +7 -0
- package/src/search-client.ts +32 -0
- package/src/search-worker.ts +38 -0
- package/src/semantic.ts +12 -0
- package/src/shiki-document.ts +19 -0
- package/src/shiki-worker.ts +68 -0
- package/src/shiki.ts +37 -0
- package/src/testing.ts +17 -0
- package/src/theme-scopes.ts +14 -0
- package/src/tokens.ts +31 -0
- package/src/touch.ts +16 -0
- package/src/tracked-range.ts +5 -0
- package/src/uniform-geometry.ts +10 -0
- package/src/view.ts +481 -75
- package/src/worker-client.ts +54 -0
package/src/layout.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface SubRowBand {
|
|
|
21
21
|
export function subRowBand(top: number, subRows: number, lineHeight: number, window: VerticalWindow, block = 1): SubRowBand {
|
|
22
22
|
const first = Math.max(0, Math.min(subRows - 1, Math.floor((window.top - top) / lineHeight)));
|
|
23
23
|
const last = Math.max(first, Math.min(subRows - 1, Math.ceil((window.bottom - top) / lineHeight) - 1));
|
|
24
|
+
|
|
24
25
|
return {
|
|
25
26
|
first: Math.floor(first / block) * block,
|
|
26
27
|
last: Math.min(subRows - 1, Math.ceil((last + 1) / block) * block - 1)
|
|
@@ -194,22 +195,27 @@ const WRAP_INDENT_STEP = 2;
|
|
|
194
195
|
const GEOMETRY_CACHE_SIZE = 400;
|
|
195
196
|
const PLAIN = /^[\x20-\x7e\t]*$/;
|
|
196
197
|
const GRAPHEME_LIMIT = 20000;
|
|
198
|
+
|
|
197
199
|
let segmenter: Intl.Segmenter | undefined;
|
|
198
200
|
|
|
199
201
|
/* Where each character starts, with the length last. A very long line is cut by code unit, since splitting it by grapheme would cost more than it shows. */
|
|
200
202
|
function characterOffsets(text: string): number[] {
|
|
201
203
|
const offsets: number[] = [];
|
|
204
|
+
|
|
202
205
|
if (text.length > GRAPHEME_LIMIT || PLAIN.test(text)) {
|
|
203
206
|
for (let i = 0; i < text.length; i++) {
|
|
204
207
|
offsets.push(i);
|
|
205
208
|
}
|
|
206
209
|
} else {
|
|
207
210
|
segmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
211
|
+
|
|
208
212
|
for (const part of segmenter.segment(text)) {
|
|
209
213
|
offsets.push(part.index);
|
|
210
214
|
}
|
|
211
215
|
}
|
|
216
|
+
|
|
212
217
|
offsets.push(text.length);
|
|
218
|
+
|
|
213
219
|
return offsets;
|
|
214
220
|
}
|
|
215
221
|
|
|
@@ -220,23 +226,29 @@ function isSpace(character: string | undefined): boolean {
|
|
|
220
226
|
function characterWidth(metrics: LayoutMetrics, text: string, x: number): number {
|
|
221
227
|
if (text === '\t') {
|
|
222
228
|
const tab = metrics.tabSize * metrics.charWidth;
|
|
229
|
+
|
|
223
230
|
return (Math.floor(x / tab) + 1) * tab - x;
|
|
224
231
|
}
|
|
232
|
+
|
|
225
233
|
if (text.length === 1 && text >= ' ' && text <= '~') {
|
|
226
234
|
return metrics.charWidth;
|
|
227
235
|
}
|
|
236
|
+
|
|
228
237
|
return bidiControl(text) !== null ? BIDI_COLUMNS * metrics.charWidth : metrics.measureText(text);
|
|
229
238
|
}
|
|
230
239
|
|
|
231
240
|
/* Where the continuation rows of a wrapped line start, kept to half the row so a deep indent never leaves no room for text. */
|
|
232
241
|
export function wrapIndent(text: string, metrics: LayoutMetrics, limit: number): number {
|
|
233
242
|
let x = 0;
|
|
243
|
+
|
|
234
244
|
for (const character of text) {
|
|
235
245
|
if (!isSpace(character)) {
|
|
236
246
|
break;
|
|
237
247
|
}
|
|
248
|
+
|
|
238
249
|
x += characterWidth(metrics, character, x);
|
|
239
250
|
}
|
|
251
|
+
|
|
240
252
|
return Math.min(x + WRAP_INDENT_STEP * metrics.charWidth, limit / 2);
|
|
241
253
|
}
|
|
242
254
|
|
|
@@ -253,9 +265,11 @@ function wrapRows(text: string, offsets: readonly number[] | null, metrics: Layo
|
|
|
253
265
|
let opportunity = -1;
|
|
254
266
|
let x = 0;
|
|
255
267
|
let index = 0;
|
|
268
|
+
|
|
256
269
|
while (index < count) {
|
|
257
270
|
const character = offsets === null ? text[index]! : text.slice(offsets[index]!, offsets[index + 1]!);
|
|
258
271
|
const width = characterWidth(metrics, character, x);
|
|
272
|
+
|
|
259
273
|
if (index > rowStart && x + width > limit && !isSpace(character)) {
|
|
260
274
|
const target = opportunity > rowStart ? opportunity : index;
|
|
261
275
|
breaks.push(target);
|
|
@@ -264,15 +278,21 @@ function wrapRows(text: string, offsets: readonly number[] | null, metrics: Layo
|
|
|
264
278
|
opportunity = -1;
|
|
265
279
|
x = indent;
|
|
266
280
|
index = target;
|
|
281
|
+
|
|
267
282
|
continue;
|
|
268
283
|
}
|
|
284
|
+
|
|
269
285
|
x += width;
|
|
286
|
+
|
|
270
287
|
if (isSpace(character) && index + 1 < count && !isSpace(text[offsets === null ? index + 1 : offsets[index + 1]!])) {
|
|
271
288
|
opportunity = index + 1;
|
|
272
289
|
}
|
|
290
|
+
|
|
273
291
|
index++;
|
|
274
292
|
}
|
|
293
|
+
|
|
275
294
|
triggers.push(count);
|
|
295
|
+
|
|
276
296
|
return { breaks, triggers };
|
|
277
297
|
}
|
|
278
298
|
|
|
@@ -292,6 +312,7 @@ export function scanDenseLine(
|
|
|
292
312
|
const wrapped = limit === null ? { breaks: [], triggers: [offsets.length - 1] } : wrapRows(text, offsets, metrics, limit);
|
|
293
313
|
const breakStops = wrapped.breaks;
|
|
294
314
|
const indent = limit === null ? 0 : wrapIndent(text, metrics, limit);
|
|
315
|
+
|
|
295
316
|
const geometry: LineGeometry = {
|
|
296
317
|
start: line.start,
|
|
297
318
|
end: line.end,
|
|
@@ -309,20 +330,25 @@ export function scanDenseLine(
|
|
|
309
330
|
subRows: breakStops.length + 1,
|
|
310
331
|
width: 0
|
|
311
332
|
};
|
|
333
|
+
|
|
312
334
|
let x = 0;
|
|
313
335
|
let row = 0;
|
|
314
336
|
let nextBreak = 0;
|
|
315
337
|
let nextInlay = 0;
|
|
316
338
|
let runStart = -1;
|
|
317
339
|
let runX = 0;
|
|
340
|
+
|
|
318
341
|
const flushRun = (end: number): void => {
|
|
319
342
|
if (runStart >= 0 && end > runStart) {
|
|
320
343
|
geometry.runs.push({ from: runStart, to: end, x: runX, subRow: row, text: text.slice(runStart, end) });
|
|
321
344
|
}
|
|
345
|
+
|
|
322
346
|
runStart = -1;
|
|
323
347
|
};
|
|
348
|
+
|
|
324
349
|
for (let stop = 0; stop < offsets.length; stop++) {
|
|
325
350
|
const offset = offsets[stop]!;
|
|
351
|
+
|
|
326
352
|
if (nextBreak < breakStops.length && breakStops[nextBreak] === stop) {
|
|
327
353
|
flushRun(offset);
|
|
328
354
|
geometry.rowEnds.push(x);
|
|
@@ -331,38 +357,50 @@ export function scanDenseLine(
|
|
|
331
357
|
row++;
|
|
332
358
|
x = indent;
|
|
333
359
|
}
|
|
360
|
+
|
|
334
361
|
geometry.before.push(x);
|
|
335
362
|
geometry.rowOf.push(row);
|
|
363
|
+
|
|
336
364
|
while (nextInlay < lineInlays.length && lineInlays[nextInlay]!.at - line.start <= offset) {
|
|
337
365
|
flushRun(offset);
|
|
338
366
|
const inlay = lineInlays[nextInlay++]!;
|
|
339
367
|
const shown = visibleBidi(inlay.text);
|
|
368
|
+
|
|
340
369
|
const width =
|
|
341
370
|
inlay.composition || inlay.ghost
|
|
342
371
|
? metrics.measureText(shown)
|
|
343
372
|
: Math.max(8, metrics.measureInlay(shown) + 12) + (inlay.gapLeft ?? 0) + (inlay.gapRight ?? 0);
|
|
373
|
+
|
|
344
374
|
geometry.inlays.push({ inlay, x, width, subRow: row });
|
|
345
375
|
x += width;
|
|
346
376
|
}
|
|
377
|
+
|
|
347
378
|
geometry.after.push(x);
|
|
379
|
+
|
|
348
380
|
if (stop === offsets.length - 1) {
|
|
349
381
|
break;
|
|
350
382
|
}
|
|
383
|
+
|
|
351
384
|
const character = text.slice(offset, offsets[stop + 1]!);
|
|
352
385
|
const width = characterWidth(metrics, character, x);
|
|
386
|
+
|
|
353
387
|
if (character === '\t' || bidiControl(character) !== null) {
|
|
354
388
|
flushRun(offset);
|
|
355
389
|
} else if (runStart < 0) {
|
|
356
390
|
runStart = offset;
|
|
357
391
|
runX = x;
|
|
358
392
|
}
|
|
393
|
+
|
|
359
394
|
x += width;
|
|
360
395
|
}
|
|
396
|
+
|
|
361
397
|
flushRun(text.length);
|
|
362
398
|
geometry.rowEnds.push(x);
|
|
399
|
+
|
|
363
400
|
for (const end of geometry.rowEnds) {
|
|
364
401
|
geometry.width = Math.max(geometry.width, end);
|
|
365
402
|
}
|
|
403
|
+
|
|
366
404
|
return geometry;
|
|
367
405
|
}
|
|
368
406
|
|
|
@@ -375,10 +413,12 @@ export function scanLine(
|
|
|
375
413
|
if (limit !== null && line.text.length > GRAPHEME_LIMIT && lineInlays.length === 0 && /^[\x20-\x7e]*$/.test(line.text)) {
|
|
376
414
|
const wrapped = wrapRows(line.text, null, metrics, limit);
|
|
377
415
|
const geometry = uniformGeometry(line, metrics, wrapped.breaks, wrapped.triggers, wrapIndent(line.text, metrics, limit));
|
|
416
|
+
|
|
378
417
|
if (geometry !== null) {
|
|
379
418
|
return geometry;
|
|
380
419
|
}
|
|
381
420
|
}
|
|
421
|
+
|
|
382
422
|
return scanDenseLine(line, metrics, limit, lineInlays);
|
|
383
423
|
}
|
|
384
424
|
|
|
@@ -395,7 +435,6 @@ export interface LayoutConfig {
|
|
|
395
435
|
replacements?: readonly BlockReplacement[];
|
|
396
436
|
}
|
|
397
437
|
|
|
398
|
-
/* Lines `first` through `last`, both included. */
|
|
399
438
|
interface LineRange {
|
|
400
439
|
first: number;
|
|
401
440
|
last: number;
|
|
@@ -449,14 +488,17 @@ class LineRow implements TextRow {
|
|
|
449
488
|
function mergeRanges(ranges: LineRange[]): LineRange[] {
|
|
450
489
|
ranges.sort((left, right) => left.first - right.first);
|
|
451
490
|
const merged: LineRange[] = [];
|
|
491
|
+
|
|
452
492
|
for (const range of ranges) {
|
|
453
493
|
const previous = merged.at(-1);
|
|
494
|
+
|
|
454
495
|
if (previous !== undefined && range.first <= previous.last + 1) {
|
|
455
496
|
previous.last = Math.max(previous.last, range.last);
|
|
456
497
|
} else {
|
|
457
498
|
merged.push({ ...range });
|
|
458
499
|
}
|
|
459
500
|
}
|
|
501
|
+
|
|
460
502
|
return merged;
|
|
461
503
|
}
|
|
462
504
|
|
|
@@ -503,30 +545,38 @@ export class EditorLayout {
|
|
|
503
545
|
|
|
504
546
|
configure(config: LayoutConfig): void {
|
|
505
547
|
let measureAgain = false;
|
|
548
|
+
|
|
506
549
|
if (config.metrics) {
|
|
507
550
|
this.metrics = config.metrics;
|
|
508
551
|
this.measuredHeights.clear();
|
|
509
552
|
measureAgain = true;
|
|
510
553
|
}
|
|
554
|
+
|
|
511
555
|
if (config.wrapWidth !== undefined && config.wrapWidth !== this.wrapWidth) {
|
|
512
556
|
this.wrapWidth = config.wrapWidth;
|
|
513
557
|
measureAgain = true;
|
|
514
558
|
}
|
|
559
|
+
|
|
515
560
|
if (config.inlays) {
|
|
516
561
|
this.inlays = config.inlays;
|
|
517
562
|
}
|
|
563
|
+
|
|
518
564
|
if (config.blocks) {
|
|
519
565
|
this.blocks = config.blocks;
|
|
520
566
|
}
|
|
567
|
+
|
|
521
568
|
if (config.folds) {
|
|
522
569
|
this.folds = config.folds;
|
|
523
570
|
this.lineHeightsVersion++;
|
|
524
571
|
}
|
|
572
|
+
|
|
525
573
|
if (config.replacements) {
|
|
526
574
|
this.replacements = config.replacements;
|
|
527
575
|
this.lineHeightsVersion++;
|
|
528
576
|
}
|
|
577
|
+
|
|
529
578
|
let edit: LineEdit | undefined;
|
|
579
|
+
|
|
530
580
|
if (measureAgain || (config.textChanged && !config.changes?.length)) {
|
|
531
581
|
this.lineHeightsVersion++;
|
|
532
582
|
this.geometryCache.clear();
|
|
@@ -538,6 +588,7 @@ export class EditorLayout {
|
|
|
538
588
|
this.retainGeometry(config.changes);
|
|
539
589
|
this.lineHeightsVersion++;
|
|
540
590
|
}
|
|
591
|
+
|
|
541
592
|
if (config.inlays !== undefined) {
|
|
542
593
|
if (!config.changes?.length) {
|
|
543
594
|
for (const line of this.inlaysByLine.keys()) {
|
|
@@ -545,20 +596,25 @@ export class EditorLayout {
|
|
|
545
596
|
this.measuredRows.delete(line);
|
|
546
597
|
}
|
|
547
598
|
}
|
|
599
|
+
|
|
548
600
|
for (const inlay of config.inlays) {
|
|
549
601
|
const line = this.document.positionAt(inlay.at).line;
|
|
550
602
|
this.geometryCache.delete(line);
|
|
551
603
|
this.measuredRows.delete(line);
|
|
552
604
|
}
|
|
605
|
+
|
|
553
606
|
this.lineHeightsVersion++;
|
|
554
607
|
}
|
|
608
|
+
|
|
555
609
|
this.width = 0;
|
|
610
|
+
|
|
556
611
|
if (this.wrapWidth === null) {
|
|
557
612
|
for (const geometry of this.geometryCache.values()) {
|
|
558
613
|
this.width = Math.max(this.width, geometry.width + ADDITIONAL_COLUMNS * this.metrics.charWidth);
|
|
559
614
|
}
|
|
560
615
|
}
|
|
561
616
|
}
|
|
617
|
+
|
|
562
618
|
this.rebuild(edit);
|
|
563
619
|
}
|
|
564
620
|
|
|
@@ -566,69 +622,91 @@ export class EditorLayout {
|
|
|
566
622
|
private lineEdit(batches: readonly (readonly DocumentChange[])[]): LineEdit | undefined {
|
|
567
623
|
let from = Number.POSITIVE_INFINITY;
|
|
568
624
|
let to = Number.NEGATIVE_INFINITY;
|
|
625
|
+
|
|
569
626
|
for (const changes of batches) {
|
|
570
627
|
if (Number.isFinite(to)) {
|
|
571
628
|
to = offsetMapper(changes)(to);
|
|
572
629
|
}
|
|
630
|
+
|
|
573
631
|
let delta = 0;
|
|
632
|
+
|
|
574
633
|
for (const change of changes) {
|
|
575
634
|
from = Math.min(from, change.from);
|
|
576
635
|
to = Math.max(to, change.from + delta + change.insertedLength);
|
|
577
636
|
delta += change.insertedLength - (change.to - change.from);
|
|
578
637
|
}
|
|
579
638
|
}
|
|
639
|
+
|
|
580
640
|
if (!Number.isFinite(from)) {
|
|
581
641
|
return undefined;
|
|
582
642
|
}
|
|
643
|
+
|
|
583
644
|
// The line above too, since an edit at the start of a line can turn the `\r` that ends it into a `\r\n`.
|
|
584
645
|
const first = Math.max(0, this.document.positionAt(from).line - 1);
|
|
585
646
|
const newLast = this.document.positionAt(to).line;
|
|
586
647
|
const oldLast = newLast - (this.document.getLineCount() - this.lineRows.length);
|
|
648
|
+
|
|
587
649
|
return oldLast < first ? undefined : { first, oldLast, newLast };
|
|
588
650
|
}
|
|
589
651
|
|
|
590
652
|
private retainGeometry(batches: readonly (readonly DocumentChange[])[]): void {
|
|
591
653
|
const map = lineChangeMapper(batches);
|
|
654
|
+
|
|
592
655
|
const remap = (bounds: { start: number; end: number; next: number }): { line: number; start: number; end: number; next: number } | null => {
|
|
593
656
|
const next = map({ start: bounds.start, end: Math.max(bounds.end, bounds.next - 1) });
|
|
657
|
+
|
|
594
658
|
if (next === null) {
|
|
595
659
|
return null;
|
|
596
660
|
}
|
|
661
|
+
|
|
597
662
|
const delta = next.start - bounds.start;
|
|
663
|
+
|
|
598
664
|
return { start: next.start, end: bounds.end + delta, next: bounds.next + delta, line: this.document.positionAt(next.start).line };
|
|
599
665
|
};
|
|
666
|
+
|
|
600
667
|
const geometries = new Map<number, LineGeometry>();
|
|
668
|
+
|
|
601
669
|
for (const geometry of this.geometryCache.values()) {
|
|
602
670
|
const next = remap(geometry);
|
|
671
|
+
|
|
603
672
|
if (next !== null && geometry.inlays.length === 0) {
|
|
604
673
|
geometries.set(next.line, next.start === geometry.start ? geometry : { ...geometry, start: next.start, end: next.end, next: next.next });
|
|
605
674
|
}
|
|
606
675
|
}
|
|
676
|
+
|
|
607
677
|
const measured = new Map<number, { start: number; end: number; next: number; subRows: number }>();
|
|
678
|
+
|
|
608
679
|
for (const [line, bounds] of this.measuredRows) {
|
|
609
680
|
const next = remap(bounds);
|
|
681
|
+
|
|
610
682
|
if (next !== null && !this.inlaysByLine.has(line)) {
|
|
611
683
|
measured.set(next.line, { start: next.start, end: next.end, next: next.next, subRows: bounds.subRows });
|
|
612
684
|
}
|
|
613
685
|
}
|
|
686
|
+
|
|
614
687
|
this.geometryCache = geometries;
|
|
615
688
|
this.measuredRows = measured;
|
|
616
689
|
}
|
|
617
690
|
|
|
618
691
|
private rowCountOf(line: number, length: number): number {
|
|
619
692
|
const limit = this.wrapWidth;
|
|
693
|
+
|
|
620
694
|
if (limit === null) {
|
|
621
695
|
return 1;
|
|
622
696
|
}
|
|
697
|
+
|
|
623
698
|
const measured = this.measuredRows.get(line);
|
|
699
|
+
|
|
624
700
|
if (measured !== undefined) {
|
|
625
701
|
return measured.subRows;
|
|
626
702
|
}
|
|
703
|
+
|
|
627
704
|
return Math.max(1, Math.ceil((length * this.metrics.charWidth) / Math.max(limit, this.metrics.charWidth)));
|
|
628
705
|
}
|
|
629
706
|
|
|
630
707
|
private rebuild(edit?: LineEdit): void {
|
|
631
708
|
const structure = this.structure();
|
|
709
|
+
|
|
632
710
|
if (edit === undefined || !this.patch(edit, structure)) {
|
|
633
711
|
this.keyed.clear();
|
|
634
712
|
const built = this.buildRows(structure, 0, this.document.getLineCount() - 1, 0);
|
|
@@ -636,52 +714,73 @@ export class EditorLayout {
|
|
|
636
714
|
this.lineRows = built.lineRows;
|
|
637
715
|
this.reflow(0);
|
|
638
716
|
}
|
|
717
|
+
|
|
639
718
|
this.special = structure.special;
|
|
640
719
|
}
|
|
641
720
|
|
|
642
721
|
private structure(): RowStructure {
|
|
643
722
|
this.inlaysByLine.clear();
|
|
723
|
+
|
|
644
724
|
for (const inlay of this.inlays) {
|
|
645
725
|
const line = this.document.positionAt(inlay.at).line;
|
|
646
726
|
const list = this.inlaysByLine.get(line) ?? [];
|
|
647
727
|
list.push(inlay);
|
|
648
728
|
this.inlaysByLine.set(line, list);
|
|
649
729
|
}
|
|
730
|
+
|
|
650
731
|
for (const list of this.inlaysByLine.values()) {
|
|
651
732
|
list.sort((left, right) => left.at - right.at);
|
|
652
733
|
}
|
|
734
|
+
|
|
653
735
|
const above = new Map<number, BlockWidget[]>();
|
|
654
736
|
const below = new Map<number, BlockWidget[]>();
|
|
737
|
+
|
|
655
738
|
for (const block of this.blocks) {
|
|
656
739
|
const line = this.document.positionAt(block.at).line;
|
|
657
740
|
const target = block.placement === 'above' ? above : below;
|
|
658
|
-
|
|
741
|
+
const list = target.get(line);
|
|
742
|
+
|
|
743
|
+
if (list === undefined) {
|
|
744
|
+
target.set(line, [block]);
|
|
745
|
+
} else {
|
|
746
|
+
list.push(block);
|
|
747
|
+
}
|
|
659
748
|
}
|
|
749
|
+
|
|
660
750
|
const folds = new Map<number, FoldState>();
|
|
751
|
+
|
|
661
752
|
for (const fold of this.folds) {
|
|
662
753
|
if (fold.collapsed && fold.endLine > (folds.get(fold.startLine)?.endLine ?? 0)) {
|
|
663
754
|
folds.set(fold.startLine, fold);
|
|
664
755
|
}
|
|
665
756
|
}
|
|
757
|
+
|
|
666
758
|
const replaced = new Map<number, { replacement: BlockReplacement; endLine: number }>();
|
|
759
|
+
|
|
667
760
|
for (const replacement of this.replacements) {
|
|
668
761
|
const startLine = this.document.positionAt(replacement.from).line;
|
|
669
762
|
const endLine = Math.max(startLine, this.document.positionAt(replacement.to).line);
|
|
763
|
+
|
|
670
764
|
// A fold the person collapsed shows its own placeholder, so folding a replaced comment keeps working.
|
|
671
765
|
if (!folds.has(startLine) && !replaced.has(startLine)) {
|
|
672
766
|
replaced.set(startLine, { replacement, endLine });
|
|
673
767
|
}
|
|
674
768
|
}
|
|
769
|
+
|
|
675
770
|
const special: LineRange[] = [];
|
|
771
|
+
|
|
676
772
|
for (const line of [...this.inlaysByLine.keys(), ...above.keys(), ...below.keys()]) {
|
|
677
773
|
special.push({ first: line, last: line });
|
|
678
774
|
}
|
|
775
|
+
|
|
679
776
|
for (const fold of folds.values()) {
|
|
680
777
|
special.push({ first: fold.startLine, last: Math.max(fold.startLine, fold.endLine) });
|
|
681
778
|
}
|
|
779
|
+
|
|
682
780
|
for (const [startLine, entry] of replaced) {
|
|
683
781
|
special.push({ first: startLine, last: entry.endLine });
|
|
684
782
|
}
|
|
783
|
+
|
|
685
784
|
return { above, below, folds, replaced, special: mergeRanges(special) };
|
|
686
785
|
}
|
|
687
786
|
|
|
@@ -692,84 +791,109 @@ export class EditorLayout {
|
|
|
692
791
|
private patch(edit: LineEdit, structure: RowStructure): boolean {
|
|
693
792
|
const count = this.document.getLineCount();
|
|
694
793
|
const delta = edit.newLast - edit.oldLast;
|
|
794
|
+
|
|
695
795
|
if (this.rows.length === 0 || this.lineRows.length + delta !== count) {
|
|
696
796
|
return false;
|
|
697
797
|
}
|
|
798
|
+
|
|
698
799
|
const moved = (range: LineRange): LineRange => {
|
|
699
800
|
if (range.last < edit.first) {
|
|
700
801
|
return range;
|
|
701
802
|
}
|
|
803
|
+
|
|
702
804
|
if (range.first > edit.oldLast) {
|
|
703
805
|
return { first: range.first + delta, last: range.last + delta };
|
|
704
806
|
}
|
|
807
|
+
|
|
705
808
|
return { first: Math.min(range.first, edit.first), last: range.last > edit.oldLast ? range.last + delta : edit.newLast };
|
|
706
809
|
};
|
|
810
|
+
|
|
707
811
|
const ranges = mergeRanges([{ first: edit.first, last: edit.newLast }, ...structure.special, ...this.special.map(moved)]).filter(
|
|
708
812
|
(range) => range.first < count
|
|
709
813
|
);
|
|
814
|
+
|
|
710
815
|
let lines = 0;
|
|
816
|
+
|
|
711
817
|
for (const range of ranges) {
|
|
712
818
|
range.last = Math.min(range.last, count - 1);
|
|
713
819
|
lines += range.last - range.first + 1;
|
|
714
820
|
}
|
|
821
|
+
|
|
715
822
|
if (lines > Math.min(PATCH_LIMIT, count / 2)) {
|
|
716
823
|
return false;
|
|
717
824
|
}
|
|
825
|
+
|
|
718
826
|
const rows = this.rows;
|
|
719
827
|
const built = new Set<LayoutRow>();
|
|
720
828
|
let first = rows.length;
|
|
721
829
|
let resized = delta !== 0;
|
|
830
|
+
|
|
722
831
|
// From the bottom up, so the rows and lines of the ranges above keep their indices. Rows not built again keep their old lines until the end.
|
|
723
832
|
for (let index = ranges.length - 1; index >= 0; index--) {
|
|
724
833
|
const range = ranges[index]!;
|
|
725
834
|
const oldFirst = range.first > edit.newLast ? range.first - delta : range.first;
|
|
726
835
|
const oldLast = range.last >= edit.newLast ? range.last - delta : range.last;
|
|
727
836
|
let start = this.lineRows[oldFirst]!;
|
|
837
|
+
|
|
728
838
|
while (start > 0 && rows[start - 1]!.kind === 'block' && rows[start - 1]!.line >= oldFirst) {
|
|
729
839
|
start--;
|
|
730
840
|
}
|
|
841
|
+
|
|
731
842
|
let end = this.lineRows[oldLast]!;
|
|
843
|
+
|
|
732
844
|
while (end + 1 < rows.length && rows[end + 1]!.kind === 'block' && rows[end + 1]!.line <= oldLast) {
|
|
733
845
|
end++;
|
|
734
846
|
}
|
|
847
|
+
|
|
735
848
|
const fresh = this.buildRows(structure, range.first, range.last, start);
|
|
736
849
|
const removed = rows.splice(start, end - start + 1, ...fresh.rows);
|
|
737
850
|
this.lineRows.splice(oldFirst, oldLast - oldFirst + 1, ...fresh.lineRows);
|
|
738
851
|
let height = 0;
|
|
852
|
+
|
|
739
853
|
for (const row of removed) {
|
|
740
854
|
height -= row.height;
|
|
855
|
+
|
|
741
856
|
if (this.keyed.get(row.key) === row) {
|
|
742
857
|
this.keyed.delete(row.key);
|
|
743
858
|
}
|
|
744
859
|
}
|
|
860
|
+
|
|
745
861
|
let top = start > 0 ? rows[start - 1]!.top + rows[start - 1]!.height : 0;
|
|
862
|
+
|
|
746
863
|
for (const row of fresh.rows) {
|
|
747
864
|
built.add(row);
|
|
748
865
|
height += row.height;
|
|
749
866
|
row.top = top;
|
|
750
867
|
top += row.height;
|
|
751
868
|
}
|
|
869
|
+
|
|
752
870
|
resized ||= height !== 0 || removed.length !== fresh.rows.length;
|
|
753
871
|
first = start;
|
|
754
872
|
}
|
|
873
|
+
|
|
755
874
|
if (resized) {
|
|
756
875
|
for (let index = first; index < rows.length; index++) {
|
|
757
876
|
const row = rows[index]!;
|
|
758
877
|
row.index = index;
|
|
878
|
+
|
|
759
879
|
if (!built.has(row) && row.line > edit.oldLast) {
|
|
760
880
|
row.line += delta;
|
|
881
|
+
|
|
761
882
|
if (row.kind === 'text') {
|
|
762
883
|
row.lastLine += delta;
|
|
763
884
|
}
|
|
764
885
|
}
|
|
886
|
+
|
|
765
887
|
if (row.kind === 'text') {
|
|
766
888
|
for (let covered = row.line; covered <= row.lastLine; covered++) {
|
|
767
889
|
this.lineRows[covered] = index;
|
|
768
890
|
}
|
|
769
891
|
}
|
|
770
892
|
}
|
|
893
|
+
|
|
771
894
|
this.reflow(first);
|
|
772
895
|
}
|
|
896
|
+
|
|
773
897
|
return true;
|
|
774
898
|
}
|
|
775
899
|
|
|
@@ -779,8 +903,10 @@ export class EditorLayout {
|
|
|
779
903
|
const count = this.document.getLineCount();
|
|
780
904
|
const rows: LayoutRow[] = [];
|
|
781
905
|
const lineRows = new Array<number>(to - from + 1);
|
|
906
|
+
|
|
782
907
|
const addBlock = (widget: BlockWidget, line: number): void => {
|
|
783
908
|
const key = `block:${widget.id}`;
|
|
909
|
+
|
|
784
910
|
const row: BlockRow = {
|
|
785
911
|
key,
|
|
786
912
|
kind: 'block',
|
|
@@ -794,18 +920,23 @@ export class EditorLayout {
|
|
|
794
920
|
? Math.max(1, widget.height ?? DEFAULT_BLOCK_HEIGHT)
|
|
795
921
|
: (this.measuredHeights.get(key) ?? widget.height ?? DEFAULT_BLOCK_HEIGHT)
|
|
796
922
|
};
|
|
923
|
+
|
|
797
924
|
rows.push(row);
|
|
798
925
|
this.keyed.set(key, row);
|
|
799
926
|
};
|
|
927
|
+
|
|
800
928
|
for (let line = from; line <= to; line++) {
|
|
801
929
|
for (const block of above.get(line) ?? []) {
|
|
802
930
|
addBlock(block, line);
|
|
803
931
|
}
|
|
932
|
+
|
|
804
933
|
const standIn = replaced.get(line);
|
|
934
|
+
|
|
805
935
|
if (standIn !== undefined) {
|
|
806
936
|
const last = Math.min(count - 1, standIn.endLine);
|
|
807
937
|
const key = `replaced:${standIn.replacement.id}`;
|
|
808
938
|
const rowIndex = index + rows.length;
|
|
939
|
+
|
|
809
940
|
const row: TextRow = {
|
|
810
941
|
key,
|
|
811
942
|
kind: 'text',
|
|
@@ -819,17 +950,23 @@ export class EditorLayout {
|
|
|
819
950
|
height: this.measuredHeights.get(key) ?? standIn.replacement.height ?? DEFAULT_BLOCK_HEIGHT,
|
|
820
951
|
replacement: standIn.replacement
|
|
821
952
|
};
|
|
953
|
+
|
|
822
954
|
rows.push(row);
|
|
823
955
|
this.keyed.set(key, row);
|
|
956
|
+
|
|
824
957
|
for (let covered = line; covered <= last; covered++) {
|
|
825
958
|
lineRows[covered - from] = rowIndex;
|
|
826
959
|
}
|
|
960
|
+
|
|
827
961
|
for (const block of below.get(last) ?? []) {
|
|
828
962
|
addBlock(block, last);
|
|
829
963
|
}
|
|
964
|
+
|
|
830
965
|
line = last;
|
|
966
|
+
|
|
831
967
|
continue;
|
|
832
968
|
}
|
|
969
|
+
|
|
833
970
|
const fold = folds.get(line);
|
|
834
971
|
const last = Math.max(line, Math.min(count - 1, fold?.endLine ?? line));
|
|
835
972
|
const tail = fold?.closer !== undefined && last > line ? this.clampToLine(fold.closer, last) : null;
|
|
@@ -838,20 +975,25 @@ export class EditorLayout {
|
|
|
838
975
|
const bounds = this.wrapWidth === null ? null : this.document.getLine(line);
|
|
839
976
|
const subRows = this.rowCountOf(line, bounds === null ? 0 : bounds.end - bounds.start);
|
|
840
977
|
rows.push(new LineRow(rowIndex, line, last, tail, head, subRows, subRows * this.metrics.lineHeight));
|
|
978
|
+
|
|
841
979
|
for (let covered = line; covered <= last; covered++) {
|
|
842
980
|
lineRows[covered - from] = rowIndex;
|
|
843
981
|
}
|
|
982
|
+
|
|
844
983
|
for (const block of below.get(line) ?? []) {
|
|
845
984
|
addBlock(block, line);
|
|
846
985
|
}
|
|
986
|
+
|
|
847
987
|
line = last;
|
|
848
988
|
}
|
|
989
|
+
|
|
849
990
|
return { rows, lineRows };
|
|
850
991
|
}
|
|
851
992
|
|
|
852
993
|
/* An offset of a fold clamped to its line, since the fold may be a moment older than an edit. */
|
|
853
994
|
private clampToLine(offset: number, line: number): number {
|
|
854
995
|
const bounds = this.document.getLine(line);
|
|
996
|
+
|
|
855
997
|
return Math.min(bounds.end, Math.max(bounds.start, offset));
|
|
856
998
|
}
|
|
857
999
|
|
|
@@ -859,11 +1001,13 @@ export class EditorLayout {
|
|
|
859
1001
|
private reflow(from: number): void {
|
|
860
1002
|
const previous = this.rows[from - 1];
|
|
861
1003
|
let top = previous === undefined ? 0 : previous.top + previous.height;
|
|
1004
|
+
|
|
862
1005
|
for (let index = from; index < this.rows.length; index++) {
|
|
863
1006
|
const row = this.rows[index]!;
|
|
864
1007
|
row.top = top;
|
|
865
1008
|
top += row.height;
|
|
866
1009
|
}
|
|
1010
|
+
|
|
867
1011
|
this.height = top + BOTTOM_PADDING;
|
|
868
1012
|
}
|
|
869
1013
|
|
|
@@ -871,8 +1015,10 @@ export class EditorLayout {
|
|
|
871
1015
|
rowByKey(key: string): LayoutRow | undefined {
|
|
872
1016
|
if (key.startsWith('line:')) {
|
|
873
1017
|
const row = this.rowForLine(Number(key.slice(5)));
|
|
1018
|
+
|
|
874
1019
|
return row.key === key ? row : undefined;
|
|
875
1020
|
}
|
|
1021
|
+
|
|
876
1022
|
return this.keyed.get(key);
|
|
877
1023
|
}
|
|
878
1024
|
|
|
@@ -880,71 +1026,89 @@ export class EditorLayout {
|
|
|
880
1026
|
setMeasuredHeight(key: string, height: number): boolean {
|
|
881
1027
|
const row = this.rowByKey(key);
|
|
882
1028
|
const next = Math.max(1, Math.round(height));
|
|
1029
|
+
|
|
883
1030
|
if (!row || (row.kind === 'block' && row.widget.fixed) || !Number.isFinite(next) || row.height === next) {
|
|
884
1031
|
return false;
|
|
885
1032
|
}
|
|
1033
|
+
|
|
886
1034
|
this.measuredHeights.set(key, next);
|
|
887
1035
|
row.height = next;
|
|
888
1036
|
this.reflow(row.index);
|
|
1037
|
+
|
|
889
1038
|
return true;
|
|
890
1039
|
}
|
|
891
1040
|
|
|
892
1041
|
/* The visual lines a drawn row really has, which its estimate may have missed. Returns whether any row changed. */
|
|
893
1042
|
syncRows(rows: readonly LayoutRow[]): boolean {
|
|
894
1043
|
let first = this.rows.length;
|
|
1044
|
+
|
|
895
1045
|
for (const row of rows) {
|
|
896
1046
|
if (row.kind !== 'text' || row.replacement !== undefined) {
|
|
897
1047
|
continue;
|
|
898
1048
|
}
|
|
1049
|
+
|
|
899
1050
|
const geometry = this.geometry(row);
|
|
900
1051
|
const subRows = geometry.subRows;
|
|
1052
|
+
|
|
901
1053
|
if (row.head === null && this.wrapWidth !== null) {
|
|
902
1054
|
if (!this.measuredRows.has(row.line) && this.measuredRows.size >= GEOMETRY_CACHE_SIZE) {
|
|
903
1055
|
this.measuredRows.delete(this.measuredRows.keys().next().value!);
|
|
904
1056
|
}
|
|
1057
|
+
|
|
905
1058
|
this.measuredRows.set(row.line, { start: geometry.start, end: geometry.end, next: geometry.next, subRows });
|
|
906
1059
|
} else {
|
|
907
1060
|
this.measuredRows.delete(row.line);
|
|
908
1061
|
}
|
|
1062
|
+
|
|
909
1063
|
if (subRows !== row.subRows) {
|
|
910
1064
|
row.subRows = subRows;
|
|
911
1065
|
row.height = subRows * this.metrics.lineHeight;
|
|
912
1066
|
first = Math.min(first, row.index);
|
|
913
1067
|
}
|
|
914
1068
|
}
|
|
1069
|
+
|
|
915
1070
|
if (first < this.rows.length) {
|
|
916
1071
|
this.lineHeightsVersion++;
|
|
917
1072
|
this.reflow(first);
|
|
1073
|
+
|
|
918
1074
|
return true;
|
|
919
1075
|
}
|
|
1076
|
+
|
|
920
1077
|
return false;
|
|
921
1078
|
}
|
|
922
1079
|
|
|
923
1080
|
rowAt(y: number): LayoutRow {
|
|
924
1081
|
let low = 0;
|
|
925
1082
|
let high = this.rows.length - 1;
|
|
1083
|
+
|
|
926
1084
|
while (low < high) {
|
|
927
1085
|
const middle = Math.ceil((low + high) / 2);
|
|
1086
|
+
|
|
928
1087
|
if (this.rows[middle]!.top <= y) {
|
|
929
1088
|
low = middle;
|
|
930
1089
|
} else {
|
|
931
1090
|
high = middle - 1;
|
|
932
1091
|
}
|
|
933
1092
|
}
|
|
1093
|
+
|
|
934
1094
|
return this.rows[low]!;
|
|
935
1095
|
}
|
|
936
1096
|
|
|
937
1097
|
/* Where a line's text starts after its indentation, which is where a lens row begins. */
|
|
938
1098
|
indentX(line: number): number {
|
|
939
1099
|
const row = this.rowForLine(line);
|
|
1100
|
+
|
|
940
1101
|
if (row.kind !== 'text') {
|
|
941
1102
|
return TEXT_PADDING;
|
|
942
1103
|
}
|
|
1104
|
+
|
|
943
1105
|
const geometry = this.geometry(row);
|
|
944
1106
|
let stop = 0;
|
|
1107
|
+
|
|
945
1108
|
while (stop < geometry.text.length && isSpace(geometry.text[stop])) {
|
|
946
1109
|
stop++;
|
|
947
1110
|
}
|
|
1111
|
+
|
|
948
1112
|
return TEXT_PADDING + xOfStop(geometry, Math.min(stop, stopCount(geometry) - 1));
|
|
949
1113
|
}
|
|
950
1114
|
|
|
@@ -952,9 +1116,9 @@ export class EditorLayout {
|
|
|
952
1116
|
return this.rows[this.lineRows[Math.max(0, Math.min(this.lineRows.length - 1, line))]!]!;
|
|
953
1117
|
}
|
|
954
1118
|
|
|
955
|
-
/* Whether a fold hides the line. */
|
|
956
1119
|
isHidden(line: number): boolean {
|
|
957
1120
|
const row = this.rowForLine(line);
|
|
1121
|
+
|
|
958
1122
|
return row.kind === 'text' && line > row.line && line <= row.lastLine;
|
|
959
1123
|
}
|
|
960
1124
|
|
|
@@ -962,14 +1126,17 @@ export class EditorLayout {
|
|
|
962
1126
|
hides(offset: number): boolean {
|
|
963
1127
|
const line = this.document.positionAt(offset).line;
|
|
964
1128
|
const row = this.rowForLine(line);
|
|
1129
|
+
|
|
965
1130
|
return row.kind === 'text' && (this.isHidden(line) || (row.head !== null && line === row.line && offset > row.head));
|
|
966
1131
|
}
|
|
967
1132
|
|
|
968
1133
|
visibleRows(top: number, height: number, overscan = 120): LayoutRow[] {
|
|
969
1134
|
const result: LayoutRow[] = [];
|
|
1135
|
+
|
|
970
1136
|
for (let index = this.rowAt(top - overscan).index; index < this.rows.length && this.rows[index]!.top < top + height + overscan; index++) {
|
|
971
1137
|
result.push(this.rows[index]!);
|
|
972
1138
|
}
|
|
1139
|
+
|
|
973
1140
|
return result;
|
|
974
1141
|
}
|
|
975
1142
|
|
|
@@ -978,6 +1145,7 @@ export class EditorLayout {
|
|
|
978
1145
|
// The first line of a fold that starts within it, laid out up to there; seldom drawn, so never kept.
|
|
979
1146
|
const line = this.document.getLine(row.line);
|
|
980
1147
|
const shown = { ...line, end: row.head, text: line.text.slice(0, row.head - line.start) };
|
|
1148
|
+
|
|
981
1149
|
return scanLine(
|
|
982
1150
|
shown,
|
|
983
1151
|
this.metrics,
|
|
@@ -985,18 +1153,25 @@ export class EditorLayout {
|
|
|
985
1153
|
(this.inlaysByLine.get(row.line) ?? []).filter((inlay) => inlay.at <= row.head!)
|
|
986
1154
|
);
|
|
987
1155
|
}
|
|
1156
|
+
|
|
988
1157
|
const cached = this.geometryCache.get(row.line);
|
|
1158
|
+
|
|
989
1159
|
if (cached) {
|
|
990
1160
|
return cached;
|
|
991
1161
|
}
|
|
1162
|
+
|
|
992
1163
|
const result = scanLine(this.document.getLine(row.line), this.metrics, this.wrapWidth, this.inlaysByLine.get(row.line) ?? []);
|
|
1164
|
+
|
|
993
1165
|
if (this.wrapWidth === null) {
|
|
994
1166
|
this.width = Math.max(this.width, result.width + ADDITIONAL_COLUMNS * this.metrics.charWidth);
|
|
995
1167
|
}
|
|
1168
|
+
|
|
996
1169
|
if (this.geometryCache.size >= GEOMETRY_CACHE_SIZE) {
|
|
997
1170
|
this.geometryCache.delete(this.geometryCache.keys().next().value!);
|
|
998
1171
|
}
|
|
1172
|
+
|
|
999
1173
|
this.geometryCache.set(row.line, result);
|
|
1174
|
+
|
|
1000
1175
|
return result;
|
|
1001
1176
|
}
|
|
1002
1177
|
|
|
@@ -1014,25 +1189,30 @@ export class EditorLayout {
|
|
|
1014
1189
|
*/
|
|
1015
1190
|
offsetAtX(geometry: LineGeometry, subRow: number, x: number): { offset: number; rowEnd: boolean } {
|
|
1016
1191
|
const local = x - TEXT_PADDING;
|
|
1192
|
+
|
|
1017
1193
|
for (const box of geometry.inlays) {
|
|
1018
1194
|
if (box.subRow === subRow && local >= box.x && local <= box.x + box.width) {
|
|
1019
1195
|
return { offset: box.inlay.at, rowEnd: false };
|
|
1020
1196
|
}
|
|
1021
1197
|
}
|
|
1198
|
+
|
|
1022
1199
|
const first = geometry.rowStarts[subRow] ?? 0;
|
|
1023
1200
|
const last = subRow + 1 < geometry.rowStarts.length ? geometry.rowStarts[subRow + 1]! : stopCount(geometry) - 1;
|
|
1024
1201
|
const wrapped = subRow + 1 < geometry.rowStarts.length;
|
|
1025
1202
|
let best = first;
|
|
1026
1203
|
let distance = Math.abs(local - xOfStop(geometry, first));
|
|
1204
|
+
|
|
1027
1205
|
// The stop that starts the next visual line stands in for the end of this one.
|
|
1028
1206
|
for (let stop = first + 1; stop <= last; stop++) {
|
|
1029
1207
|
const edge = stop === last && wrapped ? geometry.rowEnds[subRow]! : xOfStop(geometry, stop);
|
|
1030
1208
|
const candidate = Math.abs(local - edge);
|
|
1209
|
+
|
|
1031
1210
|
if (candidate < distance) {
|
|
1032
1211
|
distance = candidate;
|
|
1033
1212
|
best = stop;
|
|
1034
1213
|
}
|
|
1035
1214
|
}
|
|
1215
|
+
|
|
1036
1216
|
return { offset: geometry.start + offsetOfStop(geometry, best), rowEnd: wrapped && best === last };
|
|
1037
1217
|
}
|
|
1038
1218
|
|
|
@@ -1040,12 +1220,15 @@ export class EditorLayout {
|
|
|
1040
1220
|
caret(offset: number, affinity: Affinity = 'after', rowEnd = false): LayoutRect {
|
|
1041
1221
|
const position = this.document.positionAt(offset);
|
|
1042
1222
|
const row = this.rowForLine(position.line);
|
|
1223
|
+
|
|
1043
1224
|
if (row.kind !== 'text') {
|
|
1044
1225
|
return { x: TEXT_PADDING, y: row.top, width: 1, height: Math.min(row.height, this.metrics.lineHeight) };
|
|
1045
1226
|
}
|
|
1227
|
+
|
|
1046
1228
|
const geometry = this.geometry(row);
|
|
1047
1229
|
const stop = this.stopOf(geometry, offset);
|
|
1048
1230
|
const subRow = rowOfStop(geometry, stop);
|
|
1231
|
+
|
|
1049
1232
|
if (rowEnd && subRow > 0 && geometry.rowStarts[subRow] === stop) {
|
|
1050
1233
|
return {
|
|
1051
1234
|
x: TEXT_PADDING + geometry.rowEnds[subRow - 1]!,
|
|
@@ -1054,6 +1237,7 @@ export class EditorLayout {
|
|
|
1054
1237
|
height: this.metrics.lineHeight
|
|
1055
1238
|
};
|
|
1056
1239
|
}
|
|
1240
|
+
|
|
1057
1241
|
return {
|
|
1058
1242
|
x: TEXT_PADDING + xOfStop(geometry, stop, affinity),
|
|
1059
1243
|
y: row.top + subRow * this.metrics.lineHeight,
|
|
@@ -1065,10 +1249,13 @@ export class EditorLayout {
|
|
|
1065
1249
|
/* A height snapped up to the top of the visual line it is in, so a page scrolls to whole lines. */
|
|
1066
1250
|
lineBase(y: number): number {
|
|
1067
1251
|
const row = this.rowAt(y);
|
|
1252
|
+
|
|
1068
1253
|
if (row.kind !== 'text') {
|
|
1069
1254
|
return y;
|
|
1070
1255
|
}
|
|
1256
|
+
|
|
1071
1257
|
const base = row.top + Math.floor((y - row.top) / this.metrics.lineHeight) * this.metrics.lineHeight;
|
|
1258
|
+
|
|
1072
1259
|
return y > base && y < base + this.metrics.lineHeight ? base : y;
|
|
1073
1260
|
}
|
|
1074
1261
|
|
|
@@ -1080,9 +1267,11 @@ export class EditorLayout {
|
|
|
1080
1267
|
const position = this.document.positionAt(offset);
|
|
1081
1268
|
const row = this.rowForLine(position.line);
|
|
1082
1269
|
const line = this.document.getLine(row.kind === 'text' ? row.line : position.line);
|
|
1270
|
+
|
|
1083
1271
|
if (row.kind !== 'text') {
|
|
1084
1272
|
return { start: line.start, end: line.end, next: line.next, y: row.top };
|
|
1085
1273
|
}
|
|
1274
|
+
|
|
1086
1275
|
const geometry = this.geometry(row);
|
|
1087
1276
|
const lastLine = this.document.getLine(row.lastLine);
|
|
1088
1277
|
const hidden = position.line > row.line;
|
|
@@ -1090,6 +1279,7 @@ export class EditorLayout {
|
|
|
1090
1279
|
const last = subRow + 1 >= geometry.subRows;
|
|
1091
1280
|
const start = line.start + offsetOfStop(geometry, geometry.rowStarts[subRow]!);
|
|
1092
1281
|
const end = last ? lastLine.end : line.start + offsetOfStop(geometry, geometry.rowStarts[subRow + 1]!);
|
|
1282
|
+
|
|
1093
1283
|
return { start, end, next: last ? lastLine.next : end, y: row.top + subRow * this.metrics.lineHeight };
|
|
1094
1284
|
}
|
|
1095
1285
|
|
|
@@ -1099,11 +1289,14 @@ export class EditorLayout {
|
|
|
1099
1289
|
|
|
1100
1290
|
hitTestRow(x: number, y: number): { offset: number; rowEnd: boolean } {
|
|
1101
1291
|
const row = this.rowAt(y);
|
|
1292
|
+
|
|
1102
1293
|
if (row.kind === 'block') {
|
|
1103
1294
|
return { offset: row.widget.at, rowEnd: false };
|
|
1104
1295
|
}
|
|
1296
|
+
|
|
1105
1297
|
const geometry = this.geometry(row);
|
|
1106
1298
|
const subRow = Math.max(0, Math.min(geometry.subRows - 1, Math.floor((y - row.top) / this.metrics.lineHeight)));
|
|
1299
|
+
|
|
1107
1300
|
return this.offsetAtX(geometry, subRow, x);
|
|
1108
1301
|
}
|
|
1109
1302
|
|
|
@@ -1111,6 +1304,7 @@ export class EditorLayout {
|
|
|
1111
1304
|
rectangles(from: number, to: number, rows: readonly LayoutRow[], window?: VerticalWindow): LayoutRect[] {
|
|
1112
1305
|
const result: LayoutRect[] = [];
|
|
1113
1306
|
const lineHeight = this.metrics.lineHeight;
|
|
1307
|
+
|
|
1114
1308
|
for (const row of rows) {
|
|
1115
1309
|
if (
|
|
1116
1310
|
row.kind !== 'text' ||
|
|
@@ -1119,14 +1313,18 @@ export class EditorLayout {
|
|
|
1119
1313
|
) {
|
|
1120
1314
|
continue;
|
|
1121
1315
|
}
|
|
1316
|
+
|
|
1122
1317
|
const geometry = this.geometry(row);
|
|
1318
|
+
|
|
1123
1319
|
if (from === to || from > geometry.end || to <= geometry.start) {
|
|
1124
1320
|
continue;
|
|
1125
1321
|
}
|
|
1322
|
+
|
|
1126
1323
|
const lastStop = stopCount(geometry) - 1;
|
|
1127
1324
|
const fromStop = this.stopOf(geometry, Math.max(from, geometry.start));
|
|
1128
1325
|
const toStop = this.stopOf(geometry, Math.min(to, geometry.end));
|
|
1129
1326
|
const band = window === undefined ? { first: 0, last: geometry.subRows - 1 } : subRowBand(row.top, geometry.subRows, lineHeight, window);
|
|
1327
|
+
|
|
1130
1328
|
for (let subRow = band.first; subRow <= band.last; subRow++) {
|
|
1131
1329
|
const last = subRow + 1 === geometry.subRows;
|
|
1132
1330
|
const rowFirst = geometry.rowStarts[subRow]!;
|
|
@@ -1134,11 +1332,14 @@ export class EditorLayout {
|
|
|
1134
1332
|
const first = Math.max(fromStop, rowFirst);
|
|
1135
1333
|
const stop = Math.min(toStop, rowEnd);
|
|
1136
1334
|
const newline = last && to > geometry.end;
|
|
1335
|
+
|
|
1137
1336
|
if (first >= stop && !newline) {
|
|
1138
1337
|
continue;
|
|
1139
1338
|
}
|
|
1339
|
+
|
|
1140
1340
|
const startX = first === fromStop ? xOfStop(geometry, first, 'after') : xOfStop(geometry, first);
|
|
1141
1341
|
const endX = stop < rowEnd || last ? xOfStop(geometry, stop) : geometry.rowEnds[subRow]!;
|
|
1342
|
+
|
|
1142
1343
|
result.push({
|
|
1143
1344
|
x: TEXT_PADDING + startX,
|
|
1144
1345
|
y: row.top + subRow * lineHeight,
|
|
@@ -1147,6 +1348,7 @@ export class EditorLayout {
|
|
|
1147
1348
|
});
|
|
1148
1349
|
}
|
|
1149
1350
|
}
|
|
1351
|
+
|
|
1150
1352
|
return result;
|
|
1151
1353
|
}
|
|
1152
1354
|
|
|
@@ -1158,43 +1360,57 @@ export class EditorLayout {
|
|
|
1158
1360
|
verticalOffset(offset: number, direction: -1 | 1, x: number, distance = this.metrics.lineHeight): number {
|
|
1159
1361
|
const caret = this.caret(offset);
|
|
1160
1362
|
let y = caret.y + direction * distance;
|
|
1363
|
+
|
|
1161
1364
|
if (y < 0) {
|
|
1162
1365
|
return 0;
|
|
1163
1366
|
}
|
|
1367
|
+
|
|
1164
1368
|
let row = this.rowAt(y);
|
|
1369
|
+
|
|
1165
1370
|
while (row.kind === 'block' || (row.replacement !== undefined && row.replacement.enterable !== true)) {
|
|
1166
1371
|
const next = this.rows[row.index + direction];
|
|
1372
|
+
|
|
1167
1373
|
if (!next) {
|
|
1168
1374
|
return direction < 0 ? 0 : this.document.getLine(this.document.getLineCount() - 1).end;
|
|
1169
1375
|
}
|
|
1376
|
+
|
|
1170
1377
|
row = next;
|
|
1171
1378
|
y = direction < 0 ? row.top + row.height - 1 : row.top;
|
|
1172
1379
|
}
|
|
1380
|
+
|
|
1173
1381
|
if (y >= this.height - BOTTOM_PADDING) {
|
|
1174
1382
|
return this.document.getLine(this.document.getLineCount() - 1).end;
|
|
1175
1383
|
}
|
|
1384
|
+
|
|
1176
1385
|
return this.hitTest(x, y);
|
|
1177
1386
|
}
|
|
1178
1387
|
|
|
1179
1388
|
/* One character left or right, over the end of a line onto the next visible one. */
|
|
1180
1389
|
horizontalOffset(offset: number, direction: -1 | 1): number {
|
|
1181
1390
|
const row = this.rowForLine(this.document.positionAt(offset).line);
|
|
1391
|
+
|
|
1182
1392
|
if (row.kind === 'text') {
|
|
1183
1393
|
const geometry = this.geometry(row);
|
|
1184
1394
|
const stop = this.stopOf(geometry, offset);
|
|
1185
1395
|
const next = stop + direction;
|
|
1396
|
+
|
|
1186
1397
|
if (next >= 0 && next < stopCount(geometry) && offset >= geometry.start && offset <= geometry.end) {
|
|
1187
1398
|
return geometry.start + offsetOfStop(geometry, next);
|
|
1188
1399
|
}
|
|
1189
1400
|
}
|
|
1401
|
+
|
|
1190
1402
|
for (let index = row.index + direction; index >= 0 && index < this.rows.length; index += direction) {
|
|
1191
1403
|
const next = this.rows[index]!;
|
|
1404
|
+
|
|
1192
1405
|
if (next.kind !== 'text') {
|
|
1193
1406
|
continue;
|
|
1194
1407
|
}
|
|
1408
|
+
|
|
1195
1409
|
const line = this.document.getLine(next.line);
|
|
1410
|
+
|
|
1196
1411
|
return direction < 0 ? line.end : line.start;
|
|
1197
1412
|
}
|
|
1413
|
+
|
|
1198
1414
|
return offset;
|
|
1199
1415
|
}
|
|
1200
1416
|
}
|