diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index c2bb911b5..a84a1f233 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -5193,6 +5193,111 @@ describe('TextEditor', () => { }) }) + describe('undo/redo restore selections of editor which initiated original change', () => { + let editor1, editor2 + + beforeEach(async () => { + editor1 = editor + editor2 = new TextEditor({buffer: editor1.buffer}) + + editor1.setText(dedent ` + aaaaaa + bbbbbb + cccccc + dddddd + eeeeee + `) + }) + + it('[editor.transact] restore selection of change-initiated-editor', () => { + editor1.setCursorBufferPosition([0, 0]); editor1.transact(() => editor1.insertText('1')) + editor2.setCursorBufferPosition([1, 0]); editor2.transact(() => editor2.insertText('2')) + editor1.setCursorBufferPosition([2, 0]); editor1.transact(() => editor1.insertText('3')) + editor2.setCursorBufferPosition([3, 0]); editor2.transact(() => editor2.insertText('4')) + + expect(editor1.getText()).toBe(dedent ` + 1aaaaaa + 2bbbbbb + 3cccccc + 4dddddd + eeeeee + `) + + editor2.setCursorBufferPosition([4, 0]) + editor1.undo(); expect(editor1.getCursorBufferPosition()).toEqual([3, 0]) + editor1.undo(); expect(editor1.getCursorBufferPosition()).toEqual([2, 0]) + editor1.undo(); expect(editor1.getCursorBufferPosition()).toEqual([1, 0]) + editor1.undo(); expect(editor1.getCursorBufferPosition()).toEqual([0, 0]) + expect(editor2.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + + editor1.redo(); expect(editor1.getCursorBufferPosition()).toEqual([0, 1]) + editor1.redo(); expect(editor1.getCursorBufferPosition()).toEqual([1, 1]) + editor1.redo(); expect(editor1.getCursorBufferPosition()).toEqual([2, 1]) + editor1.redo(); expect(editor1.getCursorBufferPosition()).toEqual([3, 1]) + expect(editor2.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + + editor1.setCursorBufferPosition([4, 0]) + editor2.undo(); expect(editor2.getCursorBufferPosition()).toEqual([3, 0]) + editor2.undo(); expect(editor2.getCursorBufferPosition()).toEqual([2, 0]) + editor2.undo(); expect(editor2.getCursorBufferPosition()).toEqual([1, 0]) + editor2.undo(); expect(editor2.getCursorBufferPosition()).toEqual([0, 0]) + expect(editor1.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + + editor2.redo(); expect(editor2.getCursorBufferPosition()).toEqual([0, 1]) + editor2.redo(); expect(editor2.getCursorBufferPosition()).toEqual([1, 1]) + editor2.redo(); expect(editor2.getCursorBufferPosition()).toEqual([2, 1]) + editor2.redo(); expect(editor2.getCursorBufferPosition()).toEqual([3, 1]) + expect(editor1.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + }) + + it('[manually group checkpoint] restore selection of change-initiated-editor', () => { + const transact = (editor, fn) => { + const checkpoint = editor.createCheckpoint() + fn() + editor.groupChangesSinceCheckpoint(checkpoint) + } + + editor1.setCursorBufferPosition([0, 0]); transact(editor1, () => editor1.insertText('1')) + editor2.setCursorBufferPosition([1, 0]); transact(editor2, () => editor2.insertText('2')) + editor1.setCursorBufferPosition([2, 0]); transact(editor1, () => editor1.insertText('3')) + editor2.setCursorBufferPosition([3, 0]); transact(editor2, () => editor2.insertText('4')) + + expect(editor1.getText()).toBe(dedent ` + 1aaaaaa + 2bbbbbb + 3cccccc + 4dddddd + eeeeee + `) + + editor2.setCursorBufferPosition([4, 0]) + editor1.undo(); expect(editor1.getCursorBufferPosition()).toEqual([3, 0]) + editor1.undo(); expect(editor1.getCursorBufferPosition()).toEqual([2, 0]) + editor1.undo(); expect(editor1.getCursorBufferPosition()).toEqual([1, 0]) + editor1.undo(); expect(editor1.getCursorBufferPosition()).toEqual([0, 0]) + expect(editor2.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + + editor1.redo(); expect(editor1.getCursorBufferPosition()).toEqual([0, 1]) + editor1.redo(); expect(editor1.getCursorBufferPosition()).toEqual([1, 1]) + editor1.redo(); expect(editor1.getCursorBufferPosition()).toEqual([2, 1]) + editor1.redo(); expect(editor1.getCursorBufferPosition()).toEqual([3, 1]) + expect(editor2.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + + editor1.setCursorBufferPosition([4, 0]) + editor2.undo(); expect(editor2.getCursorBufferPosition()).toEqual([3, 0]) + editor2.undo(); expect(editor2.getCursorBufferPosition()).toEqual([2, 0]) + editor2.undo(); expect(editor2.getCursorBufferPosition()).toEqual([1, 0]) + editor2.undo(); expect(editor2.getCursorBufferPosition()).toEqual([0, 0]) + expect(editor1.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + + editor2.redo(); expect(editor2.getCursorBufferPosition()).toEqual([0, 1]) + editor2.redo(); expect(editor2.getCursorBufferPosition()).toEqual([1, 1]) + editor2.redo(); expect(editor2.getCursorBufferPosition()).toEqual([2, 1]) + editor2.redo(); expect(editor2.getCursorBufferPosition()).toEqual([3, 1]) + expect(editor1.getCursorBufferPosition()).toEqual([4, 0]) // remain unchanged + }) + }) + describe('when the buffer is changed (via its direct api, rather than via than edit session)', () => { it('moves the cursor so it is in the same relative position of the buffer', () => { expect(editor.getCursorScreenPosition()).toEqual([0, 0]) diff --git a/src/text-editor.js b/src/text-editor.js index 5c8749cc1..d817d9713 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -225,7 +225,7 @@ class TextEditor { this.defaultMarkerLayer = this.displayLayer.addMarkerLayer() if (!this.selectionsMarkerLayer) { - this.selectionsMarkerLayer = this.addMarkerLayer({maintainHistory: true, persistent: true}) + this.selectionsMarkerLayer = this.addMarkerLayer({maintainHistory: true, persistent: true, role: 'selections'}) } this.decorationManager = new DecorationManager(this) @@ -1931,7 +1931,7 @@ class TextEditor { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) undo (options = {}) { this.ensureWritable('undo', options) - this.avoidMergingSelections(() => this.buffer.undo()) + this.avoidMergingSelections(() => this.buffer.undo({selectionsMarkerLayer: this.selectionsMarkerLayer})) this.getLastSelection().autoscroll() } @@ -1941,7 +1941,7 @@ class TextEditor { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify a read-only editor. (default: false) redo (options = {}) { this.ensureWritable('redo', options) - this.avoidMergingSelections(() => this.buffer.redo()) + this.avoidMergingSelections(() => this.buffer.redo({selectionsMarkerLayer: this.selectionsMarkerLayer})) this.getLastSelection().autoscroll() } @@ -1958,7 +1958,13 @@ class TextEditor { // still 'groupable', the two transactions are merged with respect to undo and redo. // * `fn` A {Function} to call inside the transaction. transact (groupingInterval, fn) { - return this.buffer.transact(groupingInterval, fn) + const options = {selectionsMarkerLayer: this.selectionsMarkerLayer} + if (typeof groupingInterval === 'function') { + fn = groupingInterval + } else { + options.groupingInterval = groupingInterval + } + return this.buffer.transact(options, fn) } // Extended: Abort an open transaction, undoing any operations performed so far @@ -1969,7 +1975,9 @@ class TextEditor { // with {::revertToCheckpoint} and {::groupChangesSinceCheckpoint}. // // Returns a checkpoint value. - createCheckpoint () { return this.buffer.createCheckpoint() } + createCheckpoint () { + return this.buffer.createCheckpoint({selectionsMarkerLayer: this.selectionsMarkerLayer}) + } // Extended: Revert the buffer to the state it was in when the given // checkpoint was created. @@ -1993,7 +2001,9 @@ class TextEditor { // * `checkpoint` The checkpoint from which to group changes. // // Returns a {Boolean} indicating whether the operation succeeded. - groupChangesSinceCheckpoint (checkpoint) { return this.buffer.groupChangesSinceCheckpoint(checkpoint) } + groupChangesSinceCheckpoint (checkpoint) { + return this.buffer.groupChangesSinceCheckpoint(checkpoint, {selectionsMarkerLayer: this.selectionsMarkerLayer}) + } /* Section: TextEditor Coordinates