@nocturnium/svelte-ide 1.1.0 → 1.2.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 (33) hide show
  1. package/dist/components/editor/CognitiveLoadMeter.svelte +27 -0
  2. package/dist/components/editor/ComplexityHeatLayer.svelte +157 -0
  3. package/dist/components/editor/ComplexityHeatLayer.svelte.d.ts +24 -0
  4. package/dist/components/editor/ComplexityLayer.svelte +345 -110
  5. package/dist/components/editor/ComplexityLayer.svelte.d.ts +20 -0
  6. package/dist/components/editor/ConflictZoneLayer.svelte +22 -15
  7. package/dist/components/editor/CustomEditor.svelte +81 -1
  8. package/dist/components/editor/CustomEditor.svelte.d.ts +3 -1
  9. package/dist/components/editor/EchoCursorLayer.svelte +60 -0
  10. package/dist/components/editor/PluginPreviewSandbox.svelte +43 -9
  11. package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +4 -4
  12. package/dist/components/editor/core/complexity-analyzer.d.ts +31 -0
  13. package/dist/components/editor/core/complexity-analyzer.js +479 -29
  14. package/dist/components/editor/core/conflict-predictor.d.ts +32 -0
  15. package/dist/components/editor/core/conflict-predictor.js +55 -0
  16. package/dist/components/editor/core/crdt-binding.d.ts +4 -0
  17. package/dist/components/editor/core/crdt-binding.js +34 -9
  18. package/dist/components/editor/core/echo-cursor.d.ts +18 -1
  19. package/dist/components/editor/core/echo-cursor.js +117 -6
  20. package/dist/components/editor/core/extract-function.d.ts +27 -0
  21. package/dist/components/editor/core/extract-function.js +865 -0
  22. package/dist/components/editor/core/index.d.ts +1 -0
  23. package/dist/components/editor/core/index.js +1 -0
  24. package/dist/components/editor/core/state.d.ts +38 -5
  25. package/dist/components/editor/core/state.js +175 -98
  26. package/dist/components/editor/core/timeline.js +6 -1
  27. package/dist/components/editor/editor-find.js +15 -3
  28. package/dist/components/editor/theme.d.ts +8 -0
  29. package/dist/components/editor/theme.js +52 -0
  30. package/dist/services/lsp-client.d.ts +3 -0
  31. package/dist/services/lsp-client.js +86 -14
  32. package/dist/styles/theme.css +4 -1
  33. package/package.json +1 -1
@@ -17,5 +17,6 @@ export * from './snippet-manager';
17
17
  export * from './quick-actions';
18
18
  export * from './diagnostics';
19
19
  export * from './breakpoints';
20
+ export * from './extract-function';
20
21
  export type { Position } from './state';
21
22
  export type { Diagnostic, Range } from './quick-actions';
@@ -21,3 +21,4 @@ export * from './snippet-manager';
21
21
  export * from './quick-actions';
22
22
  export * from './diagnostics';
23
23
  export * from './breakpoints';
24
+ export * from './extract-function';
@@ -36,18 +36,38 @@ export interface Line {
36
36
  /** Tokenized content for syntax highlighting */
37
37
  tokens?: TokenizedLine;
38
38
  }
39
+ /**
40
+ * A primitive document change, expressed against the document state at the
41
+ * moment it was applied.
42
+ */
43
+ export interface HistoryChange {
44
+ /** Start position of the replaced range */
45
+ from: Position;
46
+ /** End position of the replaced range before the change */
47
+ to: Position;
48
+ /** Inserted text */
49
+ text: string;
50
+ /** Removed text */
51
+ removed: string;
52
+ }
39
53
  /**
40
54
  * History entry for undo/redo
41
55
  */
42
56
  export interface HistoryEntry {
43
- /** Document content before this change */
44
- content: string;
57
+ /** Primitive changes in the order they were applied */
58
+ changes: HistoryChange[];
45
59
  /** Selection state before this change (deprecated, use cursors) */
46
60
  selection: Selection;
47
61
  /** All cursors state before this change */
48
62
  cursors?: Cursor[];
49
63
  /** Primary cursor ID */
50
64
  primaryCursorId?: string;
65
+ /** Selection state after this change (deprecated, use afterCursors) */
66
+ afterSelection: Selection;
67
+ /** All cursors state after this change */
68
+ afterCursors?: Cursor[];
69
+ /** Primary cursor ID after this change */
70
+ afterPrimaryCursorId?: string;
51
71
  /** Timestamp of the change */
52
72
  timestamp: number;
53
73
  }
@@ -289,10 +309,17 @@ export declare class EditorState {
289
309
  */
290
310
  insertTab(): void;
291
311
  /**
292
- * Save current state to history with optional grouping
293
- * @param changeType - Type of change for grouping consecutive edits
312
+ * Create or reuse a history entry for a text-editing operation.
313
+ */
314
+ private beginHistory;
315
+ /**
316
+ * Commit a history entry after its edit operation has finished.
317
+ */
318
+ private commitHistory;
319
+ /**
320
+ * Create a redo/undo entry representing the current state and inverse target.
294
321
  */
295
- private saveHistory;
322
+ private createHistoryEntryFromCurrent;
296
323
  /**
297
324
  * Undo last change
298
325
  */
@@ -301,10 +328,16 @@ export declare class EditorState {
301
328
  * Redo last undone change
302
329
  */
303
330
  redo(): boolean;
331
+ private applyHistoryChanges;
332
+ private replaceRangeInternal;
333
+ private resetHistoryGrouping;
304
334
  /**
305
335
  * Deep copy a selection to prevent reference issues in history
306
336
  */
307
337
  private deepCopySelection;
338
+ private copyPosition;
339
+ private getTextInRange;
340
+ private positionAfterText;
308
341
  private transformCursorUpdatesForInsert;
309
342
  private transformPositionForInsert;
310
343
  private transformCursorUpdatesForDelete;
@@ -349,7 +349,7 @@ export class EditorState {
349
349
  * Insert text at all cursor positions
350
350
  */
351
351
  insert(text) {
352
- this.saveHistory('insert');
352
+ const history = this.beginHistory('insert');
353
353
  // Get cursors in reverse order (bottom to top) to maintain position validity
354
354
  const cursors = this._cursorManager.getSortedCursorsReverse();
355
355
  // Store cursor updates to apply after all insertions
@@ -360,12 +360,12 @@ export class EditorState {
360
360
  if (!isSelectionEmpty(cursor.selection)) {
361
361
  const start = getSelectionStart(cursor.selection);
362
362
  const end = getSelectionEnd(cursor.selection);
363
- this.deleteRangeInternal(start, end);
363
+ this.deleteRangeInternal(start, end, history.entry.changes);
364
364
  this.transformCursorUpdatesForDelete(updates, start, end);
365
365
  currentPos = start;
366
366
  }
367
367
  // Insert text
368
- const newPos = this.insertAtInternal(currentPos, text);
368
+ const newPos = this.insertAtInternal(currentPos, text, history.entry.changes);
369
369
  this.transformCursorUpdatesForInsert(updates, currentPos, text);
370
370
  updates.push({ id: cursor.id, position: newPos });
371
371
  }
@@ -374,13 +374,22 @@ export class EditorState {
374
374
  this.emitChange({ type: 'insert', from: this.cursor, text });
375
375
  this.emitSelectionChange();
376
376
  this.emitCursorChange();
377
+ this.commitHistory(history, 'insert');
377
378
  }
378
379
  /**
379
380
  * Insert text at a specific position (internal, doesn't update cursor)
380
381
  * @returns New position after insert
381
382
  */
382
- insertAtInternal(position, text) {
383
+ insertAtInternal(position, text, changes) {
383
384
  const pos = this.clampPosition(position);
385
+ if (changes) {
386
+ changes.push({
387
+ from: this.copyPosition(pos),
388
+ to: this.copyPosition(pos),
389
+ text,
390
+ removed: ''
391
+ });
392
+ }
384
393
  const line = this._lines[pos.line];
385
394
  const textLines = text.split('\n');
386
395
  if (textLines.length === 1) {
@@ -429,10 +438,11 @@ export class EditorState {
429
438
  * Insert text at a specific position (public API - single cursor)
430
439
  */
431
440
  insertAt(position, text) {
432
- this.saveHistory('insert');
433
- const newPos = this.insertAtInternal(position, text);
441
+ const history = this.beginHistory('insert');
442
+ const newPos = this.insertAtInternal(position, text, history.entry.changes);
434
443
  this.setCursor(newPos);
435
444
  this.emitChange({ type: 'insert', from: position, text });
445
+ this.commitHistory(history, 'insert');
436
446
  }
437
447
  /**
438
448
  * Delete the current selection (all cursors)
@@ -448,13 +458,13 @@ export class EditorState {
448
458
  if (!hasAnySelection) {
449
459
  return;
450
460
  }
451
- this.saveHistory('delete');
461
+ const history = this.beginHistory('delete');
452
462
  const updates = [];
453
463
  for (const cursor of cursors) {
454
464
  if (!isSelectionEmpty(cursor.selection)) {
455
465
  const start = getSelectionStart(cursor.selection);
456
466
  const end = getSelectionEnd(cursor.selection);
457
- this.deleteRangeInternal(start, end);
467
+ this.deleteRangeInternal(start, end, history.entry.changes);
458
468
  this.transformCursorUpdatesForDelete(updates, start, end);
459
469
  updates.push({ id: cursor.id, position: start });
460
470
  }
@@ -464,33 +474,48 @@ export class EditorState {
464
474
  this.emitChange({ type: 'delete', from: this.cursor, to: this.cursor });
465
475
  this.emitSelectionChange();
466
476
  this.emitCursorChange();
477
+ this.commitHistory(history, 'delete');
467
478
  }
468
479
  /**
469
480
  * Delete a range of text (internal, doesn't update cursor)
470
481
  */
471
- deleteRangeInternal(from, to) {
472
- if (from.line === to.line) {
482
+ deleteRangeInternal(from, to, changes) {
483
+ const start = this.clampPosition(from);
484
+ const end = this.clampPosition(to);
485
+ if (comparePositions(start, end) >= 0) {
486
+ return;
487
+ }
488
+ const removed = this.getTextInRange(start, end);
489
+ if (changes) {
490
+ changes.push({
491
+ from: this.copyPosition(start),
492
+ to: this.copyPosition(end),
493
+ text: '',
494
+ removed
495
+ });
496
+ }
497
+ if (start.line === end.line) {
473
498
  // Single line deletion
474
- const line = this._lines[from.line];
475
- line.text = line.text.slice(0, from.column) + line.text.slice(to.column);
499
+ const line = this._lines[start.line];
500
+ line.text = line.text.slice(0, start.column) + line.text.slice(end.column);
476
501
  // Re-tokenize to end of document so removing a multi-line construct
477
502
  // delimiter (e.g. a closing `*/`) re-propagates state to lines below.
478
- this.retokenize(from.line, this._lines.length);
503
+ this.retokenize(start.line, this._lines.length);
479
504
  }
480
505
  else {
481
506
  // Multi-line deletion
482
- const firstLine = this._lines[from.line];
483
- const lastLine = this._lines[to.line];
507
+ const firstLine = this._lines[start.line];
508
+ const lastLine = this._lines[end.line];
484
509
  // Merge first and last lines
485
- firstLine.text = firstLine.text.slice(0, from.column) + lastLine.text.slice(to.column);
510
+ firstLine.text = firstLine.text.slice(0, start.column) + lastLine.text.slice(end.column);
486
511
  // Remove intermediate lines
487
- const removedCount = to.line - from.line;
488
- this._lines.splice(from.line + 1, removedCount);
489
- this._tokenizerStates.splice(from.line + 1, removedCount);
512
+ const removedCount = end.line - start.line;
513
+ this._lines.splice(start.line + 1, removedCount);
514
+ this._tokenizerStates.splice(start.line + 1, removedCount);
490
515
  // Renumber lines
491
- this.renumberLines(from.line + 1);
516
+ this.renumberLines(start.line + 1);
492
517
  // Retokenize from affected line to end
493
- this.retokenize(from.line, this._lines.length);
518
+ this.retokenize(start.line, this._lines.length);
494
519
  }
495
520
  }
496
521
  /**
@@ -517,35 +542,23 @@ export class EditorState {
517
542
  this.deleteSelection();
518
543
  return;
519
544
  }
520
- this.saveHistory('delete');
545
+ const history = this.beginHistory('delete');
521
546
  const updates = [];
522
547
  for (const cursor of cursors) {
523
548
  const { line, column } = cursor.selection.head;
524
549
  if (column > 0) {
525
550
  const from = { line, column: column - 1 };
526
551
  const to = { line, column };
527
- // Delete within line
528
- const currentLine = this._lines[line];
529
- currentLine.text =
530
- currentLine.text.slice(0, from.column) + currentLine.text.slice(to.column);
531
- // Re-tokenize to end of document so deleting a multi-line construct
532
- // delimiter re-propagates state to lines below.
533
- this.retokenize(line, this._lines.length);
552
+ this.deleteRangeInternal(from, to, history.entry.changes);
534
553
  this.transformCursorUpdatesForDelete(updates, from, to);
535
554
  updates.push({ id: cursor.id, position: from });
536
555
  }
537
556
  else if (line > 0) {
538
557
  // Join with previous line
539
- const prevLine = this._lines[line - 1];
540
- const currentLine = this._lines[line];
541
- const newColumn = prevLine.text.length;
558
+ const newColumn = this._lines[line - 1].text.length;
542
559
  const from = { line: line - 1, column: newColumn };
543
560
  const to = { line, column: 0 };
544
- prevLine.text += currentLine.text;
545
- this._lines.splice(line, 1);
546
- this._tokenizerStates.splice(line, 1);
547
- this.renumberLines(line);
548
- this.retokenize(line - 1, this._lines.length);
561
+ this.deleteRangeInternal(from, to, history.entry.changes);
549
562
  this.transformCursorUpdatesForDelete(updates, from, to);
550
563
  updates.push({ id: cursor.id, position: from });
551
564
  }
@@ -555,6 +568,7 @@ export class EditorState {
555
568
  this.emitChange({ type: 'delete', from: this.cursor, to: this.cursor });
556
569
  this.emitSelectionChange();
557
570
  this.emitCursorChange();
571
+ this.commitHistory(history, 'delete');
558
572
  }
559
573
  /**
560
574
  * Delete character after all cursors (delete key)
@@ -573,7 +587,7 @@ export class EditorState {
573
587
  this.deleteSelection();
574
588
  return;
575
589
  }
576
- this.saveHistory('delete');
590
+ const history = this.beginHistory('delete');
577
591
  const updates = [];
578
592
  for (const cursor of cursors) {
579
593
  const { line, column } = cursor.selection.head;
@@ -581,25 +595,15 @@ export class EditorState {
581
595
  if (column < currentLine.text.length) {
582
596
  const from = { line, column };
583
597
  const to = { line, column: column + 1 };
584
- // Delete within line
585
- currentLine.text =
586
- currentLine.text.slice(0, from.column) + currentLine.text.slice(to.column);
587
- // Re-tokenize to end of document so deleting a multi-line construct
588
- // delimiter re-propagates state to lines below.
589
- this.retokenize(line, this._lines.length);
598
+ this.deleteRangeInternal(from, to, history.entry.changes);
590
599
  this.transformCursorUpdatesForDelete(updates, from, to);
591
600
  updates.push({ id: cursor.id, position: from });
592
601
  }
593
602
  else if (line < this._lines.length - 1) {
594
603
  // Join with next line
595
- const nextLine = this._lines[line + 1];
596
604
  const from = { line, column };
597
605
  const to = { line: line + 1, column: 0 };
598
- currentLine.text += nextLine.text;
599
- this._lines.splice(line + 1, 1);
600
- this._tokenizerStates.splice(line + 1, 1);
601
- this.renumberLines(line + 1);
602
- this.retokenize(line, this._lines.length);
606
+ this.deleteRangeInternal(from, to, history.entry.changes);
603
607
  this.transformCursorUpdatesForDelete(updates, from, to);
604
608
  updates.push({ id: cursor.id, position: from });
605
609
  }
@@ -609,6 +613,7 @@ export class EditorState {
609
613
  this.emitChange({ type: 'delete', from: this.cursor, to: this.cursor });
610
614
  this.emitSelectionChange();
611
615
  this.emitCursorChange();
616
+ this.commitHistory(history, 'delete');
612
617
  }
613
618
  /**
614
619
  * Insert a new line (enter key)
@@ -627,42 +632,73 @@ export class EditorState {
627
632
  // History (Undo/Redo)
628
633
  // ============================================
629
634
  /**
630
- * Save current state to history with optional grouping
631
- * @param changeType - Type of change for grouping consecutive edits
635
+ * Create or reuse a history entry for a text-editing operation.
632
636
  */
633
- saveHistory(changeType) {
637
+ beginHistory(changeType) {
634
638
  const now = Date.now();
635
- // Check if we should merge with the last history entry
636
- const shouldMerge = changeType &&
637
- changeType === this.lastHistoryType &&
639
+ const shouldMerge = changeType === this.lastHistoryType &&
638
640
  this._undoStack.length > 0 &&
639
641
  now - this.lastHistoryTimestamp < this.historyGroupTimeout;
640
- // Save cursor state
642
+ if (shouldMerge) {
643
+ return {
644
+ entry: this._undoStack[this._undoStack.length - 1],
645
+ isNew: false
646
+ };
647
+ }
641
648
  const cursorState = this._cursorManager.clone();
642
- const entry = {
643
- content: this.getContent(),
644
- selection: this.deepCopySelection(this.selection),
645
- cursors: cursorState.cursors,
646
- primaryCursorId: cursorState.primaryId,
647
- timestamp: now
649
+ return {
650
+ entry: {
651
+ changes: [],
652
+ selection: this.deepCopySelection(this.selection),
653
+ cursors: cursorState.cursors,
654
+ primaryCursorId: cursorState.primaryId,
655
+ afterSelection: this.deepCopySelection(this.selection),
656
+ afterCursors: cursorState.cursors,
657
+ afterPrimaryCursorId: cursorState.primaryId,
658
+ timestamp: now
659
+ },
660
+ isNew: true
648
661
  };
649
- if (shouldMerge) {
650
- // Keep the original restore snapshot; only extend the group's recency.
651
- this._undoStack[this._undoStack.length - 1].timestamp = now;
662
+ }
663
+ /**
664
+ * Commit a history entry after its edit operation has finished.
665
+ */
666
+ commitHistory(history, changeType) {
667
+ if (history.entry.changes.length === 0) {
668
+ return;
652
669
  }
653
- else {
654
- this._undoStack.push(entry);
655
- // Limit history size
656
- if (this._undoStack.length > this.maxHistorySize) {
670
+ const now = Date.now();
671
+ const cursorState = this._cursorManager.clone();
672
+ history.entry.afterSelection = this.deepCopySelection(this.selection);
673
+ history.entry.afterCursors = cursorState.cursors;
674
+ history.entry.afterPrimaryCursorId = cursorState.primaryId;
675
+ history.entry.timestamp = now;
676
+ if (history.isNew) {
677
+ this._undoStack.push(history.entry);
678
+ while (this._undoStack.length > this.maxHistorySize) {
657
679
  this._undoStack.shift();
658
680
  }
659
681
  }
660
- // Update grouping state
661
682
  this.lastHistoryTimestamp = now;
662
- this.lastHistoryType = changeType ?? null;
663
- // Clear redo stack on new edit
683
+ this.lastHistoryType = changeType;
664
684
  this._redoStack = [];
665
685
  }
686
+ /**
687
+ * Create a redo/undo entry representing the current state and inverse target.
688
+ */
689
+ createHistoryEntryFromCurrent(changes) {
690
+ const cursorState = this._cursorManager.clone();
691
+ return {
692
+ changes,
693
+ selection: this.deepCopySelection(this.selection),
694
+ cursors: cursorState.cursors,
695
+ primaryCursorId: cursorState.primaryId,
696
+ afterSelection: this.deepCopySelection(this.selection),
697
+ afterCursors: cursorState.cursors,
698
+ afterPrimaryCursorId: cursorState.primaryId,
699
+ timestamp: Date.now()
700
+ };
701
+ }
666
702
  /**
667
703
  * Undo last change
668
704
  */
@@ -671,17 +707,8 @@ export class EditorState {
671
707
  if (!entry) {
672
708
  return false;
673
709
  }
674
- // Save current state to redo stack
675
- const currentCursorState = this._cursorManager.clone();
676
- this._redoStack.push({
677
- content: this.getContent(),
678
- selection: this.deepCopySelection(this.selection),
679
- cursors: currentCursorState.cursors,
680
- primaryCursorId: currentCursorState.primaryId,
681
- timestamp: Date.now()
682
- });
683
- // Restore state
684
- this.setContent(entry.content);
710
+ this._redoStack.push(this.createHistoryEntryFromCurrent(entry.changes));
711
+ this.applyHistoryChanges(entry.changes, 'undo');
685
712
  // Restore cursor state
686
713
  if (entry.cursors && entry.primaryCursorId) {
687
714
  this._cursorManager.restore(entry.cursors, entry.primaryCursorId);
@@ -690,8 +717,10 @@ export class EditorState {
690
717
  // Fallback for old history entries
691
718
  this._cursorManager.setSingleSelection(entry.selection.anchor, entry.selection.head);
692
719
  }
720
+ this.emitChange({ type: 'replace', from: { line: 0, column: 0 } });
693
721
  this.emitSelectionChange();
694
722
  this.emitCursorChange();
723
+ this.resetHistoryGrouping();
695
724
  return true;
696
725
  }
697
726
  /**
@@ -702,33 +731,53 @@ export class EditorState {
702
731
  if (!entry) {
703
732
  return false;
704
733
  }
705
- // Save current state to undo stack (with size limit)
706
- const currentCursorState = this._cursorManager.clone();
707
- this._undoStack.push({
708
- content: this.getContent(),
709
- selection: this.deepCopySelection(this.selection),
710
- cursors: currentCursorState.cursors,
711
- primaryCursorId: currentCursorState.primaryId,
712
- timestamp: Date.now()
713
- });
734
+ const undoEntry = this.createHistoryEntryFromCurrent(entry.changes);
735
+ undoEntry.selection = entry.selection;
736
+ undoEntry.cursors = entry.cursors;
737
+ undoEntry.primaryCursorId = entry.primaryCursorId;
738
+ undoEntry.afterSelection = entry.afterSelection;
739
+ undoEntry.afterCursors = entry.afterCursors;
740
+ undoEntry.afterPrimaryCursorId = entry.afterPrimaryCursorId;
741
+ this._undoStack.push(undoEntry);
714
742
  // Enforce history size limit
715
743
  while (this._undoStack.length > this.maxHistorySize) {
716
744
  this._undoStack.shift();
717
745
  }
718
- // Restore state
719
- this.setContent(entry.content);
746
+ this.applyHistoryChanges(entry.changes, 'redo');
720
747
  // Restore cursor state
721
- if (entry.cursors && entry.primaryCursorId) {
722
- this._cursorManager.restore(entry.cursors, entry.primaryCursorId);
748
+ if (entry.afterCursors && entry.afterPrimaryCursorId) {
749
+ this._cursorManager.restore(entry.afterCursors, entry.afterPrimaryCursorId);
723
750
  }
724
751
  else {
725
752
  // Fallback for old history entries
726
- this._cursorManager.setSingleSelection(entry.selection.anchor, entry.selection.head);
753
+ this._cursorManager.setSingleSelection(entry.afterSelection.anchor, entry.afterSelection.head);
727
754
  }
755
+ this.emitChange({ type: 'replace', from: { line: 0, column: 0 } });
728
756
  this.emitSelectionChange();
729
757
  this.emitCursorChange();
758
+ this.resetHistoryGrouping();
730
759
  return true;
731
760
  }
761
+ applyHistoryChanges(changes, direction) {
762
+ const ordered = direction === 'undo' ? [...changes].reverse() : changes;
763
+ for (const change of ordered) {
764
+ if (direction === 'redo') {
765
+ this.replaceRangeInternal(change.from, change.to, change.text);
766
+ }
767
+ else {
768
+ const insertedEnd = this.positionAfterText(change.from, change.text);
769
+ this.replaceRangeInternal(change.from, insertedEnd, change.removed);
770
+ }
771
+ }
772
+ }
773
+ replaceRangeInternal(from, to, text) {
774
+ this.deleteRangeInternal(from, to);
775
+ return text.length > 0 ? this.insertAtInternal(from, text) : this.clampPosition(from);
776
+ }
777
+ resetHistoryGrouping() {
778
+ this.lastHistoryTimestamp = 0;
779
+ this.lastHistoryType = null;
780
+ }
732
781
  /**
733
782
  * Deep copy a selection to prevent reference issues in history
734
783
  */
@@ -738,6 +787,34 @@ export class EditorState {
738
787
  head: { line: sel.head.line, column: sel.head.column }
739
788
  };
740
789
  }
790
+ copyPosition(pos) {
791
+ return { line: pos.line, column: pos.column };
792
+ }
793
+ getTextInRange(from, to) {
794
+ if (comparePositions(from, to) >= 0) {
795
+ return '';
796
+ }
797
+ if (from.line === to.line) {
798
+ return this._lines[from.line].text.slice(from.column, to.column);
799
+ }
800
+ const lines = [];
801
+ lines.push(this._lines[from.line].text.slice(from.column));
802
+ for (let i = from.line + 1; i < to.line; i++) {
803
+ lines.push(this._lines[i].text);
804
+ }
805
+ lines.push(this._lines[to.line].text.slice(0, to.column));
806
+ return lines.join('\n');
807
+ }
808
+ positionAfterText(from, text) {
809
+ const textLines = text.split('\n');
810
+ if (textLines.length === 1) {
811
+ return { line: from.line, column: from.column + text.length };
812
+ }
813
+ return {
814
+ line: from.line + textLines.length - 1,
815
+ column: textLines[textLines.length - 1].length
816
+ };
817
+ }
741
818
  transformCursorUpdatesForInsert(updates, at, text) {
742
819
  for (const update of updates) {
743
820
  update.position = this.transformPositionForInsert(update.position, at, text);
@@ -103,8 +103,13 @@ export class TimelineManager {
103
103
  * Capture snapshot on significant edit
104
104
  */
105
105
  captureOnEdit(content, author) {
106
+ const lastSnapshot = this.snapshots[this.snapshots.length - 1];
107
+ if (lastSnapshot?.content === content) {
108
+ this.lastContent = content;
109
+ return;
110
+ }
106
111
  const lineCount = content.split('\n').length;
107
- const lastLineCount = this.snapshots.length > 0 ? this.snapshots[this.snapshots.length - 1].lineCount : 0;
112
+ const lastLineCount = this.snapshots.length > 0 ? lastSnapshot.lineCount : 0;
108
113
  const linesDiff = Math.abs(lineCount - lastLineCount);
109
114
  const timeSinceLastSnapshot = Date.now() - this.lastSnapshotTime;
110
115
  // Capture if significant change or enough time passed
@@ -96,11 +96,23 @@ export function createEditorFind(deps) {
96
96
  const firstVisibleRow = Math.max(0, Math.floor(scrollTop / lineHeight) - 1);
97
97
  const lastVisibleRow = Math.max(0, Math.ceil((scrollTop + viewportHeight) / lineHeight) + 1);
98
98
  const rects = [];
99
- for (let i = 0; i < matches.length; i++) {
99
+ let low = 0;
100
+ let high = matches.length;
101
+ while (low < high) {
102
+ const mid = Math.floor((low + high) / 2);
103
+ const visualRow = lineToVisualRow(matches[mid].line);
104
+ if (visualRow < firstVisibleRow) {
105
+ low = mid + 1;
106
+ }
107
+ else {
108
+ high = mid;
109
+ }
110
+ }
111
+ for (let i = low; i < matches.length; i++) {
100
112
  const match = matches[i];
101
113
  const visualRow = lineToVisualRow(match.line);
102
- if (visualRow < firstVisibleRow || visualRow > lastVisibleRow)
103
- continue;
114
+ if (visualRow > lastVisibleRow)
115
+ break;
104
116
  const width = (match.endColumn - match.startColumn) * charWidth;
105
117
  if (width <= 0)
106
118
  continue;
@@ -16,8 +16,10 @@ export declare const tokenColors: {
16
16
  readonly number: "var(--color-nocturnium-aurora-yellow)";
17
17
  readonly keyword: "var(--color-nocturnium-aurora-purple)";
18
18
  readonly keywordControl: "var(--color-nocturnium-aurora-purple)";
19
+ readonly keywordOperator: "var(--color-nocturnium-aurora-purple)";
19
20
  readonly keywordDefinition: "var(--color-nocturnium-aurora-purple)";
20
21
  readonly keywordModule: "var(--color-nocturnium-aurora-purple)";
22
+ readonly keywordStorage: "var(--color-nocturnium-aurora-purple)";
21
23
  readonly operator: "var(--ide-text-primary)";
22
24
  readonly variable: "var(--ide-text-primary)";
23
25
  readonly variableDefinition: "var(--color-nocturnium-wave)";
@@ -26,16 +28,22 @@ export declare const tokenColors: {
26
28
  readonly functionCall: "var(--color-nocturnium-aurora-blue)";
27
29
  readonly functionDefinition: "var(--color-nocturnium-aurora-blue)";
28
30
  readonly property: "var(--color-nocturnium-wave)";
31
+ readonly propertyDefinition: "var(--color-nocturnium-wave)";
29
32
  readonly type: "var(--color-nocturnium-teal)";
30
33
  readonly typeClass: "var(--color-nocturnium-teal)";
34
+ readonly typeInterface: "var(--color-nocturnium-teal)";
35
+ readonly typeNamespace: "var(--color-nocturnium-teal)";
31
36
  readonly typeBuiltin: "var(--color-nocturnium-teal)";
32
37
  readonly constant: "var(--color-nocturnium-aurora-yellow)";
33
38
  readonly constantBoolean: "var(--color-nocturnium-aurora-yellow)";
34
39
  readonly constantNull: "var(--color-nocturnium-aurora-yellow)";
40
+ readonly constantBuiltin: "var(--color-nocturnium-aurora-yellow)";
35
41
  readonly punctuation: "var(--ide-text-secondary)";
42
+ readonly punctuationBrace: "var(--color-nocturnium-aurora-yellow)";
36
43
  readonly tag: "var(--color-nocturnium-aurora-pink)";
37
44
  readonly tagAttribute: "var(--color-nocturnium-aurora-yellow)";
38
45
  readonly tagAttributeValue: "var(--color-nocturnium-aurora-green)";
46
+ readonly tagPunctuation: "var(--ide-text-secondary)";
39
47
  readonly markupHeading: "var(--color-nocturnium-aurora-blue)";
40
48
  readonly markupLink: "var(--color-nocturnium-wave)";
41
49
  readonly markupCode: "var(--color-nocturnium-aurora-green)";