@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.
Files changed (53) hide show
  1. package/dist/controller.d.ts +1 -0
  2. package/dist/controller.js +17 -28
  3. package/dist/engine.js +2 -9
  4. package/dist/find.js +12 -13
  5. package/dist/layout.js +7 -2
  6. package/dist/paint.d.ts +2 -0
  7. package/dist/paint.js +23 -20
  8. package/dist/view.d.ts +3 -2
  9. package/dist/view.js +46 -59
  10. package/package.json +6 -6
  11. package/src/attribution.ts +14 -0
  12. package/src/code-block.ts +25 -0
  13. package/src/column-selection.ts +10 -0
  14. package/src/controller.ts +189 -28
  15. package/src/engine.ts +87 -9
  16. package/src/fake.ts +79 -0
  17. package/src/find-search.ts +20 -0
  18. package/src/find.ts +135 -14
  19. package/src/geometry-stops.ts +10 -0
  20. package/src/grammar-preparation.ts +23 -0
  21. package/src/index.ts +1 -0
  22. package/src/input.ts +13 -0
  23. package/src/keymap-table.ts +9 -0
  24. package/src/keymap.ts +25 -0
  25. package/src/layout-changes.ts +7 -0
  26. package/src/layout.ts +219 -3
  27. package/src/lens.ts +3 -0
  28. package/src/listeners.ts +1 -0
  29. package/src/metrics.ts +14 -0
  30. package/src/modifier-gesture.ts +17 -0
  31. package/src/native-input.ts +27 -0
  32. package/src/offsets.ts +10 -0
  33. package/src/outline.ts +38 -0
  34. package/src/overview.ts +13 -0
  35. package/src/paint.ts +140 -20
  36. package/src/php-html.ts +1 -0
  37. package/src/pointer.ts +50 -0
  38. package/src/scroll-animation.ts +10 -0
  39. package/src/scroll.ts +7 -0
  40. package/src/search-client.ts +32 -0
  41. package/src/search-worker.ts +38 -0
  42. package/src/semantic.ts +12 -0
  43. package/src/shiki-document.ts +19 -0
  44. package/src/shiki-worker.ts +68 -0
  45. package/src/shiki.ts +37 -0
  46. package/src/testing.ts +17 -0
  47. package/src/theme-scopes.ts +14 -0
  48. package/src/tokens.ts +31 -0
  49. package/src/touch.ts +16 -0
  50. package/src/tracked-range.ts +5 -0
  51. package/src/uniform-geometry.ts +10 -0
  52. package/src/view.ts +481 -75
  53. package/src/worker-client.ts +54 -0
package/src/paint.ts CHANGED
@@ -59,13 +59,17 @@ export function styleSpan(span: HTMLElement, token: LineToken): void {
59
59
  if (token.color !== '') {
60
60
  span.style.color = token.color;
61
61
  }
62
+
62
63
  if (token.fontStyle & ITALIC) {
63
64
  span.style.fontStyle = 'italic';
64
65
  }
66
+
65
67
  if (token.fontStyle & BOLD) {
66
68
  span.style.fontWeight = 'bold';
67
69
  }
70
+
68
71
  const decorations = [token.fontStyle & UNDERLINE ? 'underline' : '', token.fontStyle & STRIKETHROUGH ? 'line-through' : ''].filter(Boolean);
72
+
69
73
  if (decorations.length > 0) {
70
74
  span.style.textDecoration = decorations.join(' ');
71
75
  }
@@ -75,9 +79,12 @@ export function styleSpan(span: HTMLElement, token: LineToken): void {
75
79
  function setText(element: HTMLElement, text: string, whitespace: boolean): void {
76
80
  if (!whitespace || !text.includes(' ')) {
77
81
  element.textContent = text;
82
+
78
83
  return;
79
84
  }
85
+
80
86
  const document = element.ownerDocument;
87
+
81
88
  for (const part of text.split(/( +)/)) {
82
89
  if (part.startsWith(' ')) {
83
90
  const dots = document.createElement('span');
@@ -102,20 +109,26 @@ function fillRun(
102
109
  ): void {
103
110
  if (tokens === null) {
104
111
  setText(run, geometry.text.slice(from - geometry.start, to - geometry.start), whitespace);
112
+
105
113
  return;
106
114
  }
115
+
107
116
  let tokenStart = tokensFrom;
117
+
108
118
  for (const token of tokens) {
109
119
  const tokenEnd = tokenStart + token.length;
110
120
  const start = Math.max(from, tokenStart);
111
121
  const end = Math.min(to, tokenEnd);
122
+
112
123
  if (start < end) {
113
124
  const span = run.ownerDocument.createElement('span');
114
125
  setText(span, geometry.text.slice(start - geometry.start, end - geometry.start), whitespace);
115
126
  styleSpan(span, token);
116
127
  run.append(span);
117
128
  }
129
+
118
130
  tokenStart = tokenEnd;
131
+
119
132
  if (tokenStart >= to) {
120
133
  break;
121
134
  }
@@ -135,19 +148,24 @@ export function lineElement(
135
148
  element.style.width = `${geometry.width}px`;
136
149
  let first = 0;
137
150
  let last = geometry.runs.length;
151
+
138
152
  while (first < last) {
139
153
  const middle = (first + last) >>> 1;
154
+
140
155
  if (geometry.runs[middle]!.subRow < band.first) {
141
156
  first = middle + 1;
142
157
  } else {
143
158
  last = middle;
144
159
  }
145
160
  }
161
+
146
162
  for (let index = first; index < geometry.runs.length; index++) {
147
163
  const run = geometry.runs[index]!;
164
+
148
165
  if (run.subRow > band.last) {
149
166
  break;
150
167
  }
168
+
151
169
  const span = document.createElement('span');
152
170
  span.className = 'se-run';
153
171
  span.style.left = `${run.x}px`;
@@ -155,18 +173,23 @@ export function lineElement(
155
173
  fillRun(span, geometry, geometry.start + run.from, geometry.start + run.to, tokens, whitespace);
156
174
  element.append(span);
157
175
  }
176
+
158
177
  if (whitespace) {
159
178
  appendTabSigns(element, geometry, lineHeight, band);
160
179
  }
180
+
161
181
  appendBidiSigns(element, geometry, lineHeight, band);
182
+
162
183
  for (const box of geometry.inlays) {
163
184
  if (box.subRow < band.first || box.subRow > band.last) {
164
185
  continue;
165
186
  }
187
+
166
188
  const span = document.createElement('span');
167
189
  span.className = box.inlay.ghost ? 'se-ghost' : box.inlay.composition ? 'se-composition' : 'se-inlay';
168
190
  span.dataset.inlayId = box.inlay.id;
169
191
  const { parts } = box.inlay;
192
+
170
193
  if (parts === undefined) {
171
194
  span.textContent = visibleBidi(box.inlay.text);
172
195
  } else {
@@ -178,12 +201,14 @@ export function lineElement(
178
201
  span.append(element);
179
202
  });
180
203
  }
204
+
181
205
  const gapLeft = box.inlay.gapLeft ?? 0;
182
206
  span.style.left = `${box.x + gapLeft}px`;
183
207
  span.style.top = `${box.subRow * lineHeight}px`;
184
208
  span.style.width = `${box.width - gapLeft - (box.inlay.gapRight ?? 0)}px`;
185
209
  element.append(span);
186
210
  }
211
+
187
212
  return element;
188
213
  }
189
214
 
@@ -207,6 +232,7 @@ export class RowPainter {
207
232
  /* Draws the rows and takes the rest away. Returns whether a widget's height moved the layout. */
208
233
  paint(rows: readonly LayoutRow[], paint: RowPaint): boolean {
209
234
  const visible = new Set(rows.map((row) => row.key));
235
+
210
236
  for (const [key, entry] of this.rows) {
211
237
  if (!visible.has(key)) {
212
238
  this.observer?.unobserve(entry.element);
@@ -214,7 +240,9 @@ export class RowPainter {
214
240
  this.rows.delete(key);
215
241
  }
216
242
  }
243
+
217
244
  let measured = false;
245
+
218
246
  for (const row of rows) {
219
247
  if (row.kind === 'text' && row.replacement !== undefined) {
220
248
  measured = this.paintReplacement(row, row.replacement, paint) || measured;
@@ -224,22 +252,26 @@ export class RowPainter {
224
252
  measured = this.paintBlock(row, paint) || measured;
225
253
  }
226
254
  }
255
+
227
256
  return measured;
228
257
  }
229
258
 
230
259
  private paintText(row: TextRow, paint: RowPaint): void {
231
260
  const geometry = this.layout.geometry(row);
232
261
  const tokens = paint.tokensOf(row.line);
262
+
233
263
  // Block boundaries keep a scroll within the band from rebuilding its text DOM.
234
264
  const band =
235
265
  geometry.subRows <= 64
236
266
  ? { first: 0, last: geometry.subRows - 1 }
237
267
  : subRowBand(row.top, geometry.subRows, this.layout.metrics.lineHeight, paint.window, 64);
268
+
238
269
  const folded = row.lastLine > row.line;
239
270
  const lastLine = folded && row.tail !== null ? this.layout.document.getLine(row.lastLine) : null;
240
271
  const tail = lastLine === null ? '' : lastLine.text.slice(row.tail! - lastLine.start);
241
272
  const tailTokens = tail === '' ? null : paint.tokensOf(row.lastLine);
242
273
  let entry = this.rows.get(row.key);
274
+
243
275
  if (
244
276
  !entry ||
245
277
  entry.text !== geometry.text ||
@@ -253,11 +285,13 @@ export class RowPainter {
253
285
  entry.band?.last !== band.last
254
286
  ) {
255
287
  const element = this.buildText(row, geometry, tokens, paint, tail, tailTokens, band);
288
+
256
289
  if (entry) {
257
290
  entry.element.replaceWith(element);
258
291
  } else {
259
292
  this.code.append(element);
260
293
  }
294
+
261
295
  entry = {
262
296
  element,
263
297
  text: geometry.text,
@@ -270,8 +304,10 @@ export class RowPainter {
270
304
  revision: 0,
271
305
  band
272
306
  };
307
+
273
308
  this.rows.set(row.key, entry);
274
309
  }
310
+
275
311
  entry.element.style.top = `${row.top}px`;
276
312
  entry.element.style.height = `${row.height}px`;
277
313
  entry.element.dataset.line = String(row.line);
@@ -289,13 +325,14 @@ export class RowPainter {
289
325
  const document = this.code.ownerDocument;
290
326
  const lineHeight = this.layout.metrics.lineHeight;
291
327
  const element = lineElement(document, geometry, tokens, lineHeight, band, paint.whitespace);
328
+
292
329
  if (row.lastLine > row.line && band.last === geometry.subRows - 1) {
293
330
  const chip = document.createElement('button');
294
331
  chip.type = 'button';
295
332
  chip.className = 'se-fold-chip';
296
333
  chip.textContent = '…';
297
334
  chip.tabIndex = -1;
298
- const chipLeft = geometry.rowEnds[geometry.rowEnds.length - 1]! + FOLD_GAP;
335
+ const chipLeft = geometry.rowEnds.at(-1)! + FOLD_GAP;
299
336
  const chipWidth = Math.ceil(this.layout.metrics.charWidth) + FOLD_CHIP_PADDING;
300
337
  chip.style.left = `${chipLeft}px`;
301
338
  chip.style.top = `${(geometry.subRows - 1) * lineHeight}px`;
@@ -303,10 +340,12 @@ export class RowPainter {
303
340
  chip.addEventListener('pointerdown', (event) => event.stopPropagation());
304
341
  chip.addEventListener('click', () => paint.onUnfold(row.line));
305
342
  element.append(chip);
343
+
306
344
  if (tail !== '' && row.tail !== null) {
307
345
  this.appendTail(element, row, geometry, tail, tailTokens, chipLeft + chipWidth + FOLD_GAP);
308
346
  }
309
347
  }
348
+
310
349
  return element;
311
350
  }
312
351
 
@@ -315,6 +354,7 @@ export class RowPainter {
315
354
  const document = this.code.ownerDocument;
316
355
  const lineStart = this.layout.document.getLine(row.lastLine).start;
317
356
  const geometry = scanLine({ start: row.tail!, end: row.tail! + tail.length, text: tail }, this.layout.metrics, null, []);
357
+
318
358
  for (const run of geometry.runs) {
319
359
  const span = document.createElement('span');
320
360
  span.className = 'se-run';
@@ -323,6 +363,7 @@ export class RowPainter {
323
363
  fillRun(span, geometry, geometry.start + run.from, geometry.start + run.to, tokens, false, lineStart);
324
364
  element.append(span);
325
365
  }
366
+
326
367
  appendBidiSigns(element, geometry, this.layout.metrics.lineHeight, { first: 0, last: 0 }, left, (header.subRows - 1) * this.layout.metrics.lineHeight);
327
368
  }
328
369
 
@@ -330,38 +371,56 @@ export class RowPainter {
330
371
  private paintReplacement(row: TextRow, replacement: BlockReplacement, paint: RowPaint): boolean {
331
372
  const revision = replacement.revision ?? 0;
332
373
  let entry = this.rows.get(row.key);
374
+
333
375
  if (!entry) {
334
376
  const element = this.code.ownerDocument.createElement('div');
335
377
  element.className = 'se-block se-replacement';
336
378
  element.dataset.rowKey = row.key;
337
379
  element.dataset.replacementId = replacement.id;
338
380
  element.setAttribute('role', 'group');
381
+
339
382
  if (replacement.label !== undefined) {
340
383
  element.setAttribute('aria-label', replacement.label);
341
384
  }
385
+
342
386
  replacement.render(element);
343
387
  this.code.append(element);
344
- entry = { element, text: '', tokens: null, layoutVersion: 0, whitespace: false, folded: false, tail: '', tailTokens: null, revision };
345
- this.rows.set(row.key, entry);
388
+ entry = this.hostEntry(row.key, element, revision);
346
389
  } else if (entry.revision !== revision) {
347
390
  entry.element.replaceChildren();
348
391
  replacement.render(entry.element);
349
392
  entry.revision = revision;
350
393
  }
394
+
351
395
  const indent = this.layout.indentX(row.line);
352
396
  entry.element.style.top = `${row.top}px`;
353
397
  entry.element.style.left = `${indent}px`;
354
398
  entry.element.style.width = `${Math.max(80, paint.viewportWidth - indent)}px`;
355
- entry.element.style.height = '';
356
- this.observer?.observe(entry.element);
357
- const height = entry.element.getBoundingClientRect?.().height ?? 0;
358
- return height > 0 && this.layout.setMeasuredHeight(row.key, height);
399
+
400
+ return this.measure(row.key, entry.element);
401
+ }
402
+
403
+ private hostEntry(key: string, element: HTMLElement, revision: number): PaintedRow {
404
+ const entry = { element, text: '', tokens: null, layoutVersion: 0, whitespace: false, folded: false, tail: '', tailTokens: null, revision };
405
+ this.rows.set(key, entry);
406
+
407
+ return entry;
408
+ }
409
+
410
+ /* Lets the host's content set the height, and returns whether that moved the layout. */
411
+ private measure(key: string, element: HTMLElement): boolean {
412
+ element.style.height = '';
413
+ this.observer?.observe(element);
414
+ const height = element.getBoundingClientRect?.().height ?? 0;
415
+
416
+ return height > 0 && this.layout.setMeasuredHeight(key, height);
359
417
  }
360
418
 
361
419
  private paintBlock(row: Extract<LayoutRow, { kind: 'block' }>, paint: RowPaint): boolean {
362
420
  const { widget } = row;
363
421
  const revision = widget.revision ?? 0;
364
422
  let entry = this.rows.get(row.key);
423
+
365
424
  if (!entry) {
366
425
  const element = this.code.ownerDocument.createElement('div');
367
426
  element.className = 'se-block';
@@ -369,37 +428,42 @@ export class RowPainter {
369
428
  element.dataset.widgetId = widget.id;
370
429
  this.fillBlock(element, widget);
371
430
  this.code.append(element);
431
+
372
432
  if (!widget.lens && !widget.fixed) {
373
433
  this.observer?.observe(element);
374
434
  }
375
- entry = { element, text: '', tokens: null, layoutVersion: 0, whitespace: false, folded: false, tail: '', tailTokens: null, revision };
376
- this.rows.set(row.key, entry);
435
+
436
+ entry = this.hostEntry(row.key, element, revision);
377
437
  } else if (entry.revision !== revision) {
378
438
  entry.element.replaceChildren();
379
439
  this.fillBlock(entry.element, widget);
380
440
  entry.revision = revision;
381
441
  }
442
+
382
443
  entry.element.style.top = `${row.top}px`;
444
+
383
445
  if (widget.lens) {
384
446
  entry.element.style.left = `${this.layout.indentX(row.line)}px`;
385
447
  entry.element.style.height = `${row.height}px`;
448
+
386
449
  return false;
387
450
  }
451
+
388
452
  if (widget.render) {
389
453
  entry.element.style.left = `${paint.hostLeft}px`;
390
454
  entry.element.style.width = `${paint.hostWidth}px`;
391
455
  } else {
392
456
  entry.element.style.width = `${Math.max(80, paint.viewportWidth)}px`;
393
457
  }
458
+
394
459
  if (widget.fixed) {
395
460
  this.observer?.unobserve(entry.element);
396
461
  entry.element.style.height = `${row.height}px`;
462
+
397
463
  return false;
398
464
  }
399
- entry.element.style.height = '';
400
- this.observer?.observe(entry.element);
401
- const height = entry.element.getBoundingClientRect?.().height ?? 0;
402
- return height > 0 && this.layout.setMeasuredHeight(row.key, height);
465
+
466
+ return this.measure(row.key, entry.element);
403
467
  }
404
468
 
405
469
  private fillBlock(element: HTMLElement, widget: BlockWidget): void {
@@ -415,6 +479,7 @@ export class RowPainter {
415
479
  this.observer?.unobserve(entry.element);
416
480
  entry.element.remove();
417
481
  }
482
+
418
483
  this.rows.clear();
419
484
  }
420
485
  }
@@ -446,6 +511,7 @@ function gutterButton(document: Document, className: string, label: string): HTM
446
511
  button.className = className;
447
512
  button.tabIndex = -1;
448
513
  button.setAttribute('aria-label', label);
514
+
449
515
  return button;
450
516
  }
451
517
 
@@ -453,76 +519,96 @@ function gutterButton(document: Document, className: string, label: string): HTM
453
519
  export function paintGutter(container: HTMLElement, layout: EditorLayout, rows: readonly LayoutRow[], paint: GutterPaint): void {
454
520
  const document = container.ownerDocument;
455
521
  const fragment = document.createDocumentFragment();
522
+
456
523
  for (const row of rows) {
457
524
  if (row.kind !== 'text') {
458
525
  continue;
459
526
  }
527
+
460
528
  const item = document.createElement('div');
461
529
  item.className = paint.activeLines.has(row.line) ? 'se-line-number se-active-number' : 'se-line-number';
462
530
  item.style.top = `${row.top}px`;
463
531
  item.style.height = `${layout.metrics.lineHeight}px`;
464
532
  const highlight = paint.highlights.get(row.line);
465
533
  const fill = highlight?.fill;
534
+
466
535
  if (fill !== undefined) {
467
536
  item.style.background = colorValue(fill);
468
537
  }
538
+
469
539
  if (highlight?.bar !== undefined) {
470
540
  item.classList.add('se-line-marked');
471
541
  item.style.height = `${row.height}px`;
472
542
  item.style.setProperty('--se-line', `${layout.metrics.lineHeight}px`);
473
543
  item.style.setProperty('--se-mark', colorValue(highlight.color));
544
+
474
545
  if (fill === undefined) {
475
546
  item.style.setProperty('--se-tint', tintValue(highlight.color));
476
547
  }
477
548
  }
549
+
478
550
  const number = document.createElement('span');
479
551
  number.textContent = paint.lineNumber(row.line);
480
552
  const label = paint.lineLabel(row.line);
553
+
481
554
  if (label !== null) {
482
555
  number.setAttribute('aria-hidden', 'true');
483
556
  }
557
+
484
558
  item.append(number);
559
+
485
560
  if (label !== null) {
486
561
  const hidden = document.createElement('span');
487
562
  hidden.className = 'se-sr-only';
488
563
  hidden.textContent = label;
489
564
  item.append(hidden);
490
565
  }
566
+
491
567
  if (paint.action?.line === row.line) {
492
568
  const button = gutterButton(document, 'se-gutter-action', paint.action.label);
493
569
  button.dataset.gutterAction = String(row.line);
494
570
  item.append(button);
495
571
  }
572
+
496
573
  const marker = paint.markers.get(row.line);
574
+
497
575
  if (marker !== undefined) {
498
576
  const button = gutterButton(document, 'se-gutter-marker', marker.label);
499
577
  button.dataset.gutterMarker = marker.id;
578
+
500
579
  if (marker.icon !== undefined) {
501
580
  button.dataset.icon = marker.icon;
502
581
  }
582
+
503
583
  if (marker.pressed !== undefined) {
504
584
  button.setAttribute('aria-pressed', String(marker.pressed));
505
585
  }
586
+
506
587
  item.append(button);
507
588
  }
508
- const sign = paint.highlights.get(row.line)?.sign;
509
- if (sign) {
589
+
590
+ if (highlight?.sign) {
510
591
  const mark = document.createElement('span');
511
592
  mark.className = 'se-line-sign';
512
- mark.textContent = sign;
593
+ mark.textContent = highlight.sign;
594
+
513
595
  if (label !== null) {
514
596
  mark.setAttribute('aria-hidden', 'true');
515
597
  }
516
- mark.style.color = colorValue(paint.highlights.get(row.line)!.color);
598
+
599
+ mark.style.color = colorValue(highlight.color);
517
600
  item.append(mark);
518
601
  }
602
+
519
603
  if (highlight?.bar !== undefined) {
520
604
  const mark = document.createElement('span');
521
605
  mark.className = `se-line-bar se-line-bar-${highlight.bar}`;
522
606
  mark.setAttribute('aria-hidden', 'true');
523
607
  item.append(mark);
524
608
  }
609
+
525
610
  const bar = paint.attribution.get(row.line);
611
+
526
612
  if (bar) {
527
613
  const mark = document.createElement('span');
528
614
  mark.className = 'se-attribution';
@@ -531,25 +617,33 @@ export function paintGutter(container: HTMLElement, layout: EditorLayout, rows:
531
617
  mark.style.height = `${row.height}px`;
532
618
  item.append(mark);
533
619
  }
620
+
534
621
  const change = paint.changes.get(row.line);
622
+
535
623
  // The bar of an agent stands where the mark of a change would, and a removal is a triangle between lines that the bar never hides.
536
624
  if (change && (!bar || change === 'deleted') && highlight?.bar === undefined) {
537
625
  const mark = document.createElement('span');
538
626
  mark.className = `se-change se-change-${change}`;
627
+
539
628
  if (change !== 'deleted') {
540
629
  mark.style.height = `${row.height}px`;
541
630
  }
631
+
542
632
  item.append(mark);
543
633
  }
634
+
544
635
  const collapsed = paint.foldable.get(row.line);
636
+
545
637
  if (collapsed !== undefined) {
546
638
  const button = gutterButton(document, collapsed ? 'se-fold-toggle se-folded' : 'se-fold-toggle', paint.foldName(row.line, collapsed));
547
639
  button.setAttribute('aria-expanded', String(!collapsed));
548
640
  button.dataset.foldLine = String(row.line);
549
641
  item.append(button);
550
642
  }
643
+
551
644
  fragment.append(item);
552
645
  }
646
+
553
647
  container.replaceChildren(fragment);
554
648
  }
555
649
 
@@ -567,6 +661,7 @@ function box(document: Document, className: string, rect: LayoutRect): HTMLEleme
567
661
  element.style.top = `${rect.y}px`;
568
662
  element.style.width = `${Math.max(1, rect.width)}px`;
569
663
  element.style.height = `${rect.height}px`;
664
+
570
665
  return element;
571
666
  }
572
667
 
@@ -574,21 +669,27 @@ function box(document: Document, className: string, rect: LayoutRect): HTMLEleme
574
669
  export function paintOverlays(container: HTMLElement, paint: OverlayPaint): void {
575
670
  const document = container.ownerDocument;
576
671
  const fragment = document.createDocumentFragment();
672
+
577
673
  if (paint.currentLine) {
578
674
  fragment.append(box(document, 'se-current-line', paint.currentLine));
579
675
  }
676
+
580
677
  for (const mark of paint.marks) {
581
678
  for (const rect of mark.rects) {
582
679
  const element = box(document, mark.className, rect);
680
+
583
681
  if (mark.background) {
584
682
  element.style.background = mark.background;
585
683
  }
684
+
586
685
  fragment.append(element);
587
686
  }
588
687
  }
688
+
589
689
  for (const rect of paint.selections) {
590
690
  fragment.append(box(document, paint.focused ? 'se-selection' : 'se-selection se-selection-inactive', rect));
591
691
  }
692
+
592
693
  container.replaceChildren(fragment);
593
694
  }
594
695
 
@@ -596,17 +697,19 @@ export function paintOverlays(container: HTMLElement, paint: OverlayPaint): void
596
697
  export function paintOver(container: HTMLElement, layers: readonly { className: string; rects: readonly LayoutRect[] }[]): void {
597
698
  const document = container.ownerDocument;
598
699
  const fragment = document.createDocumentFragment();
700
+
599
701
  for (const layer of layers) {
600
702
  for (const rect of layer.rects) {
601
703
  fragment.append(box(document, layer.className, rect));
602
704
  }
603
705
  }
706
+
604
707
  container.replaceChildren(fragment);
605
708
  }
606
709
 
607
- /* The carets, above the text. */
608
710
  export function paintCarets(container: HTMLElement, carets: readonly (LayoutRect & { primary: boolean; drop?: boolean })[]): void {
609
711
  const document = container.ownerDocument;
712
+
610
713
  container.replaceChildren(
611
714
  ...carets.map((rect) => box(document, rect.drop ? 'se-drop-caret' : rect.primary ? 'se-caret se-primary-caret' : 'se-caret', { ...rect, width: 2 }))
612
715
  );
@@ -626,6 +729,7 @@ export interface RemoteCaret {
626
729
  /* The carets of agents with their names, above the text; they take no pointer and never move the real caret. */
627
730
  export function paintRemoteCarets(container: HTMLElement, carets: readonly RemoteCaret[]): void {
628
731
  const document = container.ownerDocument;
732
+
629
733
  container.replaceChildren(
630
734
  ...carets.map((caret) => {
631
735
  const element = box(document, 'se-remote-caret', { x: caret.x, y: caret.y, width: 2, height: caret.height });
@@ -635,10 +739,13 @@ export function paintRemoteCarets(container: HTMLElement, carets: readonly Remot
635
739
  label.className = 'se-remote-label';
636
740
  label.textContent = caret.name;
637
741
  label.style.background = colorValue(caret.color);
742
+
638
743
  if (caret.below) {
639
744
  label.dataset.below = 'true';
640
745
  }
746
+
641
747
  element.append(label);
748
+
642
749
  return element;
643
750
  })
644
751
  );
@@ -654,6 +761,7 @@ export interface StickyEntry {
654
761
  /* The headers pinned at the top: a line number in the gutter's column, then the header's own text. */
655
762
  export function paintSticky(container: HTMLElement, entries: readonly StickyEntry[], lineHeight: number): void {
656
763
  const document = container.ownerDocument;
764
+
657
765
  container.replaceChildren(
658
766
  ...entries.map((entry, depth) => {
659
767
  const row = document.createElement('div');
@@ -667,6 +775,7 @@ export function paintSticky(container: HTMLElement, entries: readonly StickyEntr
667
775
  code.className = 'se-sticky-code';
668
776
  code.append(lineElement(document, entry.geometry, entry.tokens, lineHeight, { first: 0, last: 0 }));
669
777
  row.append(number, code);
778
+
670
779
  return row;
671
780
  })
672
781
  );
@@ -695,6 +804,7 @@ export function arrowSign(document: Document, width: number, lineHeight: number,
695
804
  path.setAttribute('stroke', 'currentColor');
696
805
  path.setAttribute('stroke-width', '1');
697
806
  svg.append(path);
807
+
698
808
  return svg as SVGSVGElement;
699
809
  }
700
810
 
@@ -703,20 +813,26 @@ function appendTabSigns(element: HTMLElement, geometry: LineGeometry, lineHeight
703
813
  if (geometry.uniform !== undefined) {
704
814
  return;
705
815
  }
816
+
706
817
  const document = element.ownerDocument;
707
818
  const end = geometry.rowStarts[band.last + 1] ?? stopCount(geometry) - 1;
819
+
708
820
  for (let stop = geometry.rowStarts[band.first]!; stop < end; stop++) {
709
821
  if (geometry.text[offsetOfStop(geometry, stop)] !== '\t') {
710
822
  continue;
711
823
  }
824
+
825
+ const row = rowOfStop(geometry, stop);
712
826
  const left = xOfStop(geometry, stop);
713
- const right = rowOfStop(geometry, stop + 1) === rowOfStop(geometry, stop) ? xOfStop(geometry, stop + 1) : geometry.rowEnds[rowOfStop(geometry, stop)]!;
827
+ const right = rowOfStop(geometry, stop + 1) === row ? xOfStop(geometry, stop + 1) : geometry.rowEnds[row]!;
828
+
714
829
  if (right - left < 3) {
715
830
  continue;
716
831
  }
832
+
717
833
  const sign = arrowSign(document, right - left - 2, lineHeight, 'se-tab-sign');
718
834
  sign.style.left = `${left + 1}px`;
719
- sign.style.top = `${rowOfStop(geometry, stop) * lineHeight}px`;
835
+ sign.style.top = `${row * lineHeight}px`;
720
836
  element.append(sign);
721
837
  }
722
838
  }
@@ -729,11 +845,13 @@ function appendBidiSigns(element: HTMLElement, geometry: LineGeometry, lineHeigh
729
845
  if (geometry.uniform !== undefined) {
730
846
  return;
731
847
  }
848
+
732
849
  const document = element.ownerDocument;
733
850
  const from = offsetOfStop(geometry, geometry.rowStarts[band.first]!);
734
851
  const to = geometry.rowStarts[band.last + 1] === undefined ? geometry.text.length : offsetOfStop(geometry, geometry.rowStarts[band.last + 1]!);
735
852
  const pattern = new RegExp(BIDI_PATTERN.source, 'g');
736
853
  pattern.lastIndex = from;
854
+
737
855
  for (let match = pattern.exec(geometry.text); match !== null && match.index < to; match = pattern.exec(geometry.text)) {
738
856
  const stop = stopAtOffset(geometry, match.index);
739
857
  const row = rowOfStop(geometry, stop);
@@ -760,11 +878,13 @@ export interface WrapSign {
760
878
  /* The signs of the soft wraps: an arrow where a row ends in the middle of a line, and one at the start of the row that goes on. */
761
879
  export function paintSigns(container: HTMLElement, signs: readonly WrapSign[], lineHeight: number): void {
762
880
  const document = container.ownerDocument;
881
+
763
882
  container.replaceChildren(
764
883
  ...signs.map((sign) => {
765
884
  const arrow = arrowSign(document, sign.width, lineHeight, 'se-wrap-sign');
766
885
  arrow.style.left = `${sign.x}px`;
767
886
  arrow.style.top = `${sign.y}px`;
887
+
768
888
  return arrow;
769
889
  })
770
890
  );
package/src/php-html.ts CHANGED
@@ -38,6 +38,7 @@ export async function loadDocumentLanguage(highlighter: Highlighter, language: s
38
38
  if (language !== PHP_DOCUMENT_ID || highlighter.getLoadedLanguages().includes(PHP_DOCUMENT_ID)) {
39
39
  return;
40
40
  }
41
+
41
42
  await highlighter.loadLanguage('php');
42
43
  await highlighter.loadLanguage(PHP_DOCUMENT);
43
44
  }