* Moves the characters from `start` and `end` to `index`. * * `affinity` controls where the range is anchored at `index`. With the * default `'right'`, it is inserted before the content that starts at `index`; * with `'left'`, it is inserted after the content that ends at `index`. The
(start: number, end: number, index: number, affinity: 'left' | 'right' = 'right')
| 629 | * mirroring the `appendLeft`/`appendRight` distinction. |
| 630 | */ |
| 631 | move(start: number, end: number, index: number, affinity: 'left' | 'right' = 'right'): this { |
| 632 | start = start + this.offset |
| 633 | end = end + this.offset |
| 634 | index = index + this.offset |
| 635 | |
| 636 | if (start === end) |
| 637 | return this |
| 638 | |
| 639 | if (index >= start && index <= end) { |
| 640 | throw new MagicStringError('cannot move a selection inside itself') |
| 641 | } |
| 642 | |
| 643 | /* v8 ignore next 2 -- DEBUG is always true in tests */ |
| 644 | if (DEBUG) |
| 645 | this.stats.time('move') |
| 646 | |
| 647 | this._split(start) |
| 648 | this._split(end) |
| 649 | this._split(index) |
| 650 | |
| 651 | const first = this.byStart.get(start) |
| 652 | const last = this.byEnd.get(end) |
| 653 | |
| 654 | // The splicing below assumes the chunks spanning [start, end) are still a |
| 655 | // forward run in the current list. An earlier move can have interleaved a |
| 656 | // chunk from outside the range, or put `last` before `first`, and then the |
| 657 | // pointer rewrites produce a cycle rather than an error, so toString() and |
| 658 | // generateMap() loop forever. |
| 659 | // |
| 660 | // Only move() reorders chunks, so this is skipped until one has run, which |
| 661 | // keeps the common single-move case free of the walk. |
| 662 | if (this.hasMovedChunks) { |
| 663 | let cursor = first |
| 664 | while (cursor !== last) { |
| 665 | cursor = cursor.next |
| 666 | if (!cursor || cursor.start < start || cursor.end > end) { |
| 667 | throw new MagicStringError( |
| 668 | `cannot move ${start} to ${end} because an earlier move split that range`, |
| 669 | ) |
| 670 | } |
| 671 | } |
| 672 | } |
| 673 | |
| 674 | const oldLeft = first.previous |
| 675 | const oldRight = last.next |
| 676 | |
| 677 | // Anchor the range at `index` according to `affinity`: `'right'` puts it |
| 678 | // before the chunk starting at `index`, `'left'` after the chunk ending at |
| 679 | // `index`. When no such anchor exists (`index` is at an end of the string) |
| 680 | // the range falls to that end. |
| 681 | // |
| 682 | // The early returns catch the cases where the range is already in place: |
| 683 | // splicing it in next to itself would link the chunk list back on itself |
| 684 | // and drop the range from the output. |
| 685 | let newLeft: Chunk | null |
| 686 | let newRight: Chunk | null |
| 687 | if (affinity === 'left') { |
| 688 | newLeft = this.byEnd.get(index) ?? null |