@adecore/editor 0.39.0 → 0.40.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.
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/engine.ts CHANGED
@@ -94,6 +94,7 @@ class SmartEditor implements Editor {
94
94
  this.model = new DocumentModel(options.text);
95
95
  this.language = options.language;
96
96
  this.theme = options.theme;
97
+
97
98
  this.settings = {
98
99
  searchWorker: engine.searchWorker,
99
100
  language: options.language,
@@ -112,8 +113,10 @@ class SmartEditor implements Editor {
112
113
  foldOutline: options.foldOutline ?? 'hover',
113
114
  folding: options.folding !== false
114
115
  };
116
+
115
117
  this.view = new EditorView(element, this.model, this.settings);
116
118
  this.grammarPreparation = new GrammarPreparation(this.model, () => this.view.grammarChanged());
119
+
117
120
  this.controller = new InputController(this.view, {
118
121
  handBack: [...(engine.handBack ?? []), ...(options.handBack ?? [])],
119
122
  keymap: engine.keymap,
@@ -121,25 +124,33 @@ class SmartEditor implements Editor {
121
124
  save: () => emit(this.saves),
122
125
  blur: () => emit(this.blurs)
123
126
  });
127
+
124
128
  this.revision = this.model.getRevision();
125
129
  this.caretOffset = this.model.getPrimary().head;
130
+
126
131
  this.subscription = this.model.subscribe((snapshot) => {
127
132
  this.caretMoved(snapshot.selections.at(-1)!.head);
133
+
128
134
  if (snapshot.revision !== this.revision) {
129
135
  this.revision = snapshot.revision;
130
136
  this.grammarPreparation.edited(snapshot.changes ?? []);
137
+
131
138
  if (snapshot.contentEdits !== undefined && snapshot.contentEdits.length > 0) {
132
139
  this.announceTextChange(snapshot.contentEdits, snapshot.source ?? 'external', snapshot.typed === true);
133
140
  }
141
+
134
142
  if (!this.settingText) {
135
143
  emit(this.changes);
136
144
  }
137
145
  }
138
146
  });
147
+
139
148
  this.loadTokenizer();
149
+
140
150
  if (options.folds !== undefined || options.foldDefaults !== undefined) {
141
151
  this.view.restoreFolds(options.folds ?? null, options.foldDefaults);
142
152
  }
153
+
143
154
  this.placeCursor(options);
144
155
  }
145
156
 
@@ -150,11 +161,13 @@ class SmartEditor implements Editor {
150
161
  const bounds = this.model.getLine(line);
151
162
  const offset = Math.min(bounds.end, bounds.start + Math.max(0, (options.column ?? 1) - 1));
152
163
  this.model.setSelections([{ anchor: offset, head: offset }]);
164
+
153
165
  if (options.scrollTop === undefined) {
154
166
  // A file opens where it is told to, without travelling there.
155
167
  this.view.revealOffset(offset, 'center', false);
156
168
  }
157
169
  }
170
+
158
171
  if (options.scrollTop !== undefined) {
159
172
  this.view.render();
160
173
  this.view.viewport.scrollTop = options.scrollTop;
@@ -165,6 +178,7 @@ class SmartEditor implements Editor {
165
178
  private loadTokenizer(): void {
166
179
  const request = ++this.tokenizerRequest;
167
180
  this.grammarPreparation.begin();
181
+
168
182
  void this.engine
169
183
  .scopeColors?.(this.theme)
170
184
  .catch(() => null)
@@ -173,6 +187,7 @@ class SmartEditor implements Editor {
173
187
  this.view.setScopeColors(colors ?? null);
174
188
  }
175
189
  });
190
+
176
191
  void this.engine
177
192
  .tokenizer(this.language, this.theme, this.model.getText())
178
193
  .catch(() => null)
@@ -190,10 +205,13 @@ class SmartEditor implements Editor {
190
205
 
191
206
  setText(text: string): void {
192
207
  const spans = changedSpans(this.model.getText(), text);
208
+
193
209
  if (spans.length === 0) {
194
210
  return;
195
211
  }
212
+
196
213
  this.settingText = true;
214
+
197
215
  try {
198
216
  // Edits and not a reset, which would drop the undo history and put the cursor on line one.
199
217
  this.model.applyEdits(
@@ -222,6 +240,7 @@ class SmartEditor implements Editor {
222
240
  text: edit.text
223
241
  }))
224
242
  };
243
+
225
244
  emit(this.textChanges, change);
226
245
  }
227
246
 
@@ -229,9 +248,9 @@ class SmartEditor implements Editor {
229
248
  if (head === this.caretOffset) {
230
249
  return;
231
250
  }
251
+
232
252
  this.caretOffset = head;
233
- const position = this.positionAt(head);
234
- emit(this.carets, position);
253
+ emit(this.carets, this.positionAt(head));
235
254
  }
236
255
 
237
256
  getCaret(): EditorPosition {
@@ -293,9 +312,11 @@ class SmartEditor implements Editor {
293
312
 
294
313
  setSelections(ranges: readonly EditorRange[], reveal: EditorReveal = 'relative'): void {
295
314
  const last = ranges.at(-1);
315
+
296
316
  if (last === undefined) {
297
317
  return;
298
318
  }
319
+
299
320
  this.model.setSelections(ranges.map((range) => ({ anchor: this.offsetAt(range.start), head: this.offsetAt(range.end) })));
300
321
  this.view.revealOffset(this.offsetAt(last.end), reveal);
301
322
  }
@@ -339,8 +360,10 @@ class SmartEditor implements Editor {
339
360
  if (this.settings.readOnly || edits.length === 0) {
340
361
  return false;
341
362
  }
363
+
342
364
  try {
343
365
  const { mapSelections } = options;
366
+
344
367
  return this.model.applyEdits(
345
368
  edits.map((edit) => ({ from: this.offsetAt(edit.range.start), to: this.offsetAt(edit.range.end), text: edit.text })),
346
369
  {
@@ -352,9 +375,11 @@ class SmartEditor implements Editor {
352
375
  const { start, end } = mapSelections(this.rangeOf(selection));
353
376
  const from = { line: start.line, column: start.character };
354
377
  const to = { line: end.line, column: end.character };
378
+
355
379
  if (selection.head < selection.anchor) {
356
380
  return { anchor: to, head: from };
357
381
  }
382
+
358
383
  return {
359
384
  anchor: from,
360
385
  head: selection.head === selection.anchor && start.line === end.line && start.character === end.character ? from : to
@@ -415,6 +440,7 @@ class SmartEditor implements Editor {
415
440
  ? null
416
441
  : tokens.map((token) => {
417
442
  const from = this.offsetAt({ line: token.line, character: token.character });
443
+
418
444
  return { from, to: from + token.length, scopes: token.scopes, within: token.within };
419
445
  })
420
446
  );
@@ -432,11 +458,13 @@ class SmartEditor implements Editor {
432
458
 
433
459
  getVisibleRange(): EditorRange {
434
460
  const { first, last } = this.view.visibleLines();
461
+
435
462
  return { start: { line: first, character: 0 }, end: this.positionAt(this.model.getLine(last).end) };
436
463
  }
437
464
 
438
465
  positionAt(offset: number): EditorPosition {
439
466
  const { line, column } = this.model.positionAt(offset);
467
+
440
468
  return { line, character: column };
441
469
  }
442
470
 
@@ -473,76 +501,103 @@ class SmartEditor implements Editor {
473
501
  replace(replacement: string, replaceOptions: EditorReplaceOptions = {}): boolean {
474
502
  const { find } = this.view;
475
503
  const match = find.currentMatch;
504
+
476
505
  if (this.blockedByReadOnly()) {
477
506
  return false;
478
507
  }
508
+
479
509
  if (find.activeQuery?.regex === true) {
480
510
  if (find.currentMark === null) {
481
511
  return false;
482
512
  }
513
+
483
514
  void find.replaceOne(replacement, replaceOptions.preserveCase === true, () => !this.disposed && !this.settings.readOnly);
515
+
484
516
  return true;
485
517
  }
518
+
486
519
  if (match === null) {
487
520
  return false;
488
521
  }
522
+
489
523
  try {
490
524
  // The next match is looked for after what was written, or a replacement that holds the query would find itself again.
491
525
  find.pendingAnchor = match.to;
526
+
492
527
  if (!this.model.replace(match, replacement, { preserveCase: replaceOptions.preserveCase })) {
493
528
  find.pendingAnchor = undefined;
529
+
494
530
  return false;
495
531
  }
496
532
  } catch {
497
533
  find.pendingAnchor = undefined;
534
+
498
535
  return false;
499
536
  }
537
+
500
538
  this.view.revealFind();
501
539
  this.view.render();
540
+
502
541
  return true;
503
542
  }
504
543
 
505
544
  replaceAll(replacement: string, options: EditorReplaceOptions = {}): number {
506
545
  const { find } = this.view;
507
546
  const query = find.activeQuery;
547
+
508
548
  if (query === null || find.state.noSelection === true || this.blockedByReadOnly()) {
509
549
  return 0;
510
550
  }
551
+
511
552
  try {
512
553
  const matches = find.completeMatches;
554
+
513
555
  if (matches !== null) {
514
556
  let characters = 0;
557
+
515
558
  for (const match of matches) {
516
559
  characters += replacementText('', match, replacement, false, options.preserveCase, MAX_REPLACEMENT_CHARACTERS).length;
560
+
517
561
  if (characters > MAX_REPLACEMENT_CHARACTERS) {
518
562
  find.reportReplaceFailed('limit');
563
+
519
564
  return 0;
520
565
  }
521
566
  }
567
+
522
568
  const count = this.model.replaceMatches(matches, replacement, { preserveCase: options.preserveCase });
523
569
  find.reportReplaced(count);
570
+
524
571
  return count;
525
572
  }
573
+
526
574
  void this.replaceAllAsync(replacement, options);
575
+
527
576
  return 0;
528
577
  } catch (error) {
529
578
  find.reportReplaceFailed(error instanceof RangeError ? 'limit' : 'error');
579
+
530
580
  return 0;
531
581
  }
532
582
  }
533
583
 
534
584
  async replaceAllAsync(replacement: string, options: EditorReplaceOptions = {}): Promise<number> {
535
585
  const { find } = this.view;
586
+
536
587
  if (this.disposed || this.blockedByReadOnly() || find.activeQuery === null || find.state.noSelection) {
537
588
  return 0;
538
589
  }
590
+
539
591
  if (find.state.limited) {
540
592
  find.reportReplaceFailed('limit');
593
+
541
594
  return 0;
542
595
  }
596
+
543
597
  if (find.completeMatches !== null) {
544
598
  return this.replaceAll(replacement, options);
545
599
  }
600
+
546
601
  return find.replaceAll(replacement, options.preserveCase === true, () => !this.disposed && !this.settings.readOnly);
547
602
  }
548
603
 
@@ -553,17 +608,22 @@ class SmartEditor implements Editor {
553
608
  selectFindMatches(): number {
554
609
  const { find } = this.view;
555
610
  const marks = find.matches;
611
+
556
612
  if (marks.length === 0) {
557
613
  return 0;
558
614
  }
615
+
559
616
  const current = find.current ?? marks.length - 1;
560
617
  const ordered = [...marks.slice(0, current), ...marks.slice(current + 1), marks[current]!];
561
618
  this.view.setFind(null, false);
619
+
562
620
  for (const mark of ordered) {
563
621
  this.view.ensureVisible(mark.from);
564
622
  }
623
+
565
624
  this.model.setSelections(ordered.map((mark) => ({ anchor: mark.from, head: mark.to })));
566
625
  this.view.revealCaret();
626
+
567
627
  return ordered.length;
568
628
  }
569
629
 
@@ -571,27 +631,36 @@ class SmartEditor implements Editor {
571
631
  if (query.text === '') {
572
632
  return false;
573
633
  }
634
+
574
635
  const { anchor, head } = this.model.getPrimary();
575
636
  const offset = direction === 1 ? Math.max(anchor, head) : Math.min(anchor, head);
637
+
576
638
  const reveal = (target: { from: number; to: number } | null): boolean => {
577
639
  if (target === null || this.disposed) {
578
640
  return false;
579
641
  }
642
+
580
643
  this.view.ensureVisible(target.from);
581
644
  this.model.setSelections([direction === 1 ? { anchor: target.from, head: target.to } : { anchor: target.to, head: target.from }]);
582
645
  this.view.revealCaret(target.from > head ? 'centerDown' : 'centerUp');
646
+
583
647
  return true;
584
648
  };
649
+
585
650
  if (query.regex) {
586
651
  const revision = this.model.getRevision();
652
+
587
653
  void this.view.find.nextFromCursor(query, offset, direction).then((target) => {
588
654
  const current = this.model.getPrimary();
655
+
589
656
  if (this.model.getRevision() === revision && current.anchor === anchor && current.head === head) {
590
657
  reveal(target);
591
658
  }
592
659
  });
660
+
593
661
  return true;
594
662
  }
663
+
595
664
  try {
596
665
  return reveal(
597
666
  this.model.findNext(query.text, offset, {
@@ -607,13 +676,7 @@ class SmartEditor implements Editor {
607
676
  }
608
677
 
609
678
  private blockedByReadOnly(): boolean {
610
- if (!this.settings.readOnly) {
611
- return false;
612
- }
613
- if (this.settings.readOnlyReason) {
614
- this.view.notify(this.settings.readOnlyReason);
615
- }
616
- return true;
679
+ return this.view.blockedByReadOnly();
617
680
  }
618
681
 
619
682
  onFind(listener: (state: EditorFindState) => void): () => void {
@@ -623,6 +686,7 @@ class SmartEditor implements Editor {
623
686
  endFind(): void {
624
687
  const mark = this.view.find.currentMark;
625
688
  this.view.setFind(null, false);
689
+
626
690
  if (mark) {
627
691
  this.view.ensureVisible(mark.from);
628
692
  this.model.setSelections([{ anchor: mark.from, head: mark.to }]);
@@ -632,9 +696,11 @@ class SmartEditor implements Editor {
632
696
 
633
697
  setWrap(wrap: boolean): void {
634
698
  this.settings.wrap = wrap;
699
+
635
700
  if (wrap) {
636
701
  this.view.viewport.scrollLeft = 0;
637
702
  }
703
+
638
704
  this.view.applySettings();
639
705
  }
640
706
 
@@ -737,9 +803,11 @@ class SmartEditor implements Editor {
737
803
 
738
804
  trackRange(range: EditorRange): EditorTrackedRange {
739
805
  const tracked = this.view.trackRange(this.offsetAt(range.start), this.offsetAt(range.end));
806
+
740
807
  return {
741
808
  get: () => {
742
809
  const offsets = tracked.get();
810
+
743
811
  return offsets === null ? null : { start: this.positionAt(offsets.from), end: this.positionAt(offsets.to) };
744
812
  },
745
813
  dispose: () => tracked.dispose()
@@ -749,16 +817,20 @@ class SmartEditor implements Editor {
749
817
  setCodeVision(rows: readonly EditorCodeVision[]): void {
750
818
  const current = new Map(rows.map((row) => [row.id, row]));
751
819
  const revisions = new Map<string, { signature: string; revision: number }>();
820
+
752
821
  for (const row of rows) {
753
822
  const signature = lensSignature(row.entries);
754
823
  const before = this.lensRevisions.get(row.id);
824
+
755
825
  revisions.set(row.id, {
756
826
  signature,
757
827
  revision: before === undefined || before.signature === signature ? (before?.revision ?? 0) : before.revision + 1
758
828
  });
759
829
  }
830
+
760
831
  this.lensRows = current;
761
832
  this.lensRevisions = revisions;
833
+
762
834
  this.view.setLenses(
763
835
  rows.map((row) => ({
764
836
  id: `lens:${row.id}`,
@@ -775,6 +847,7 @@ class SmartEditor implements Editor {
775
847
  /* Where the text of a line begins after its indentation, so a row sitting there stays with the declaration when something is typed in front of it. */
776
848
  private indentOffsetOf(line: number): number {
777
849
  const { start, text } = this.model.getLine(Math.max(0, Math.min(line, this.model.getLineCount() - 1)));
850
+
778
851
  return start + (/^[ \t]*/.exec(text)?.[0].length ?? 0);
779
852
  }
780
853
 
@@ -838,7 +911,9 @@ class SmartEditor implements Editor {
838
911
 
839
912
  private foldHintsByOffset(hints: EditorFoldHints): FoldHints {
840
913
  const lineOf = (line: number): { start: number; end: number } => this.model.getLine(Math.min(line, this.model.getLineCount() - 1));
914
+
841
915
  const within = (line: { start: number; end: number }, character: number): number => Math.min(line.end, line.start + Math.max(0, character));
916
+
842
917
  return {
843
918
  symbols: (hints.symbols ?? []).map((symbol) => ({
844
919
  from: this.offsetAt(symbol.range.start),
@@ -848,6 +923,7 @@ class SmartEditor implements Editor {
848
923
  ranges: (hints.ranges ?? []).map((range) => {
849
924
  const first = lineOf(range.startLine);
850
925
  const last = lineOf(range.endLine);
926
+
851
927
  return {
852
928
  from: first.start,
853
929
  to: last.start,
@@ -867,6 +943,7 @@ class SmartEditor implements Editor {
867
943
  if (indentation.tabSize === this.settings.tabSize && indentation.insertSpaces === this.settings.insertSpaces) {
868
944
  return;
869
945
  }
946
+
870
947
  this.settings.tabSize = indentation.tabSize;
871
948
  this.settings.insertSpaces = indentation.insertSpaces;
872
949
  this.view.applySettings();
@@ -909,6 +986,7 @@ class SmartEditor implements Editor {
909
986
  if (this.disposed) {
910
987
  return;
911
988
  }
989
+
912
990
  this.disposed = true;
913
991
  this.grammarPreparation.dispose();
914
992
  this.changes.clear();