@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.
- package/dist/components/editor/CognitiveLoadMeter.svelte +27 -0
- package/dist/components/editor/ComplexityHeatLayer.svelte +157 -0
- package/dist/components/editor/ComplexityHeatLayer.svelte.d.ts +24 -0
- package/dist/components/editor/ComplexityLayer.svelte +345 -110
- package/dist/components/editor/ComplexityLayer.svelte.d.ts +20 -0
- package/dist/components/editor/ConflictZoneLayer.svelte +22 -15
- package/dist/components/editor/CustomEditor.svelte +81 -1
- package/dist/components/editor/CustomEditor.svelte.d.ts +3 -1
- package/dist/components/editor/EchoCursorLayer.svelte +60 -0
- package/dist/components/editor/PluginPreviewSandbox.svelte +43 -9
- package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +4 -4
- package/dist/components/editor/core/complexity-analyzer.d.ts +31 -0
- package/dist/components/editor/core/complexity-analyzer.js +479 -29
- package/dist/components/editor/core/conflict-predictor.d.ts +32 -0
- package/dist/components/editor/core/conflict-predictor.js +55 -0
- package/dist/components/editor/core/crdt-binding.d.ts +4 -0
- package/dist/components/editor/core/crdt-binding.js +34 -9
- package/dist/components/editor/core/echo-cursor.d.ts +18 -1
- package/dist/components/editor/core/echo-cursor.js +117 -6
- package/dist/components/editor/core/extract-function.d.ts +27 -0
- package/dist/components/editor/core/extract-function.js +865 -0
- package/dist/components/editor/core/index.d.ts +1 -0
- package/dist/components/editor/core/index.js +1 -0
- package/dist/components/editor/core/state.d.ts +38 -5
- package/dist/components/editor/core/state.js +175 -98
- package/dist/components/editor/core/timeline.js +6 -1
- package/dist/components/editor/editor-find.js +15 -3
- package/dist/components/editor/theme.d.ts +8 -0
- package/dist/components/editor/theme.js +52 -0
- package/dist/services/lsp-client.d.ts +3 -0
- package/dist/services/lsp-client.js +86 -14
- package/dist/styles/theme.css +4 -1
- 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';
|
|
@@ -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
|
-
/**
|
|
44
|
-
|
|
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
|
-
*
|
|
293
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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[
|
|
475
|
-
line.text = line.text.slice(0,
|
|
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(
|
|
503
|
+
this.retokenize(start.line, this._lines.length);
|
|
479
504
|
}
|
|
480
505
|
else {
|
|
481
506
|
// Multi-line deletion
|
|
482
|
-
const firstLine = this._lines[
|
|
483
|
-
const lastLine = this._lines[
|
|
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,
|
|
510
|
+
firstLine.text = firstLine.text.slice(0, start.column) + lastLine.text.slice(end.column);
|
|
486
511
|
// Remove intermediate lines
|
|
487
|
-
const removedCount =
|
|
488
|
-
this._lines.splice(
|
|
489
|
-
this._tokenizerStates.splice(
|
|
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(
|
|
516
|
+
this.renumberLines(start.line + 1);
|
|
492
517
|
// Retokenize from affected line to end
|
|
493
|
-
this.retokenize(
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
637
|
+
beginHistory(changeType) {
|
|
634
638
|
const now = Date.now();
|
|
635
|
-
|
|
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
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
|
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
|
-
|
|
675
|
-
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
719
|
-
this.setContent(entry.content);
|
|
746
|
+
this.applyHistoryChanges(entry.changes, 'redo');
|
|
720
747
|
// Restore cursor state
|
|
721
|
-
if (entry.
|
|
722
|
-
this._cursorManager.restore(entry.
|
|
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.
|
|
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 ?
|
|
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
|
-
|
|
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
|
|
103
|
-
|
|
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)";
|