From decb971933feaad014562b567decaedaeec2e57f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Szabo?= Date: Wed, 15 Nov 2023 16:04:02 -0300 Subject: [PATCH 1/8] Fixed onDidChangeCursorPosition for "back" delete functions --- src/cursor.js | 48 ++++++++++++++++++++++++++++++++++++++++---- src/selection.js | 52 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/src/cursor.js b/src/cursor.js index 334c5b0d8..70a5b5057 100644 --- a/src/cursor.js +++ b/src/cursor.js @@ -300,6 +300,25 @@ module.exports = class Cursor extends Model { const range = this.marker.getScreenRange(); if (moveToEndOfSelection && !range.isEmpty()) { this.setScreenPosition(range.start); + } else { + const point = this.getPreviousColumnScreenPosition( + columnCount, { moveToEndOfSelection } + ) + this.setScreenPosition(point, { clipDirection: 'backward' }); + } + } + + // Public: Retrieves the screen position of where the previous column starts. + // * `columnCount` (optional) {Number} number of columns to move (default: 1) + // * `options` (optional) {Object} with the following keys: + // * `moveToEndOfSelection` if true, move to the right of the selection if a + // selection exists. + // + // Returns a {Point}. + getPreviousColumnScreenPosition(columnCount = 1, { moveToEndOfSelection } = {}) { + const range = this.marker.getScreenRange(); + if (moveToEndOfSelection && !range.isEmpty()) { + return range.start; } else { let { row, column } = this.getScreenPosition(); @@ -310,7 +329,7 @@ module.exports = class Cursor extends Model { } column = column - columnCount; - this.setScreenPosition({ row, column }, { clipDirection: 'backward' }); + return new Point( row, column ) } } @@ -324,6 +343,25 @@ module.exports = class Cursor extends Model { const range = this.marker.getScreenRange(); if (moveToEndOfSelection && !range.isEmpty()) { this.setScreenPosition(range.end); + } else { + const point = this.getNextColumnScreenPosition( + columnCount, { moveToEndOfSelection } + ) + this.setScreenPosition(point, { clipDirection: 'forward' }); + } + } + + // Public: Retrieves the screen position of where the next column starts. + // * `columnCount` (optional) {Number} number of columns to move (default: 1) + // * `options` (optional) {Object} with the following keys: + // * `moveToEndOfSelection` if true, move to the right of the selection if a + // selection exists. + // + // Returns a {Point}. + getNextColumnScreenPosition(columnCount = 1, { moveToEndOfSelection } = {}) { + const range = this.marker.getScreenRange(); + if (moveToEndOfSelection && !range.isEmpty()) { + return range.end; } else { let { row, column } = this.getScreenPosition(); const maxLines = this.editor.getScreenLineCount(); @@ -340,7 +378,7 @@ module.exports = class Cursor extends Model { } column = column + columnCount; - this.setScreenPosition({ row, column }, { clipDirection: 'forward' }); + return new Point(row, column); } } @@ -568,7 +606,7 @@ module.exports = class Cursor extends Model { // * `allowPrevious` A {Boolean} indicating whether the beginning of the // previous word can be returned. // - // Returns a {Range}. + // Returns a {Point}. getBeginningOfCurrentWordBufferPosition(options = {}) { const allowPrevious = options.allowPrevious !== false; const position = this.getBufferPosition(); @@ -629,7 +667,7 @@ module.exports = class Cursor extends Model { // * `wordRegex` A {RegExp} indicating what constitutes a "word" // (default: {::wordRegExp}). // - // Returns a {Range} + // Returns a {Point} getBeginningOfNextWordBufferPosition(options = {}) { const currentBufferPosition = this.getBufferPosition(); const start = this.isInsideWord(options) @@ -674,6 +712,8 @@ module.exports = class Cursor extends Model { // * `options` (optional) {Object} // * `includeNewline` A {Boolean} which controls whether the Range should // include the newline. + // + // Returns a {Range}. getCurrentLineBufferRange(options) { return this.editor.bufferRangeForBufferRow(this.getBufferRow(), options); } diff --git a/src/selection.js b/src/selection.js index 8b1540272..cafb2f53f 100644 --- a/src/selection.js +++ b/src/selection.js @@ -593,8 +593,14 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) backspace(options = {}) { if (!this.ensureWritable('backspace', options)) return; - if (this.isEmpty()) this.selectLeft(); - this.deleteSelectedText(options); + + const screenPosition = this.cursor.getPreviousColumnScreenPosition() + this._deleteToPreviousPoint( + this.cursor.marker.layer.translateScreenPosition(screenPosition), + options + ); + // if (this.isEmpty()) this.selectLeft(); + // this.deleteSelectedText(options); } // Public: Removes the selection or, if nothing is selected, then all @@ -605,8 +611,12 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToPreviousWordBoundary(options = {}) { if (!this.ensureWritable('deleteToPreviousWordBoundary', options)) return; - if (this.isEmpty()) this.selectToPreviousWordBoundary(); - this.deleteSelectedText(options); + // if (this.isEmpty()) this.selectToPreviousWordBoundary(); + // this.deleteSelectedText(options); + this._deleteToPreviousPoint( + this.cursor.getPreviousWordBoundaryBufferPosition(), + options + ); } // Public: Removes the selection or, if nothing is selected, then all @@ -628,8 +638,10 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToBeginningOfWord(options = {}) { if (!this.ensureWritable('deleteToBeginningOfWord', options)) return; - if (this.isEmpty()) this.selectToBeginningOfWord(); - this.deleteSelectedText(options); + this._deleteToPreviousPoint( + this.cursor.getBeginningOfCurrentWordBufferPosition(), + options + ); } // Public: Removes from the beginning of the line which the selection begins on @@ -639,12 +651,22 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToBeginningOfLine(options = {}) { if (!this.ensureWritable('deleteToBeginningOfLine', options)) return; + const startPoint = this.getBufferRange().start; if (this.isEmpty() && this.cursor.isAtBeginningOfLine()) { - this.selectLeft(); + const lastCharPoint = this.cursor.marker.layer.translateScreenPosition( + this.cursor.getPreviousColumnScreenPosition() + ); + const prevRange = new Range( startPoint, lastCharPoint ); + this.editor.buffer.setTextInRange( + prevRange, '', pick(options, 'undo', 'normalizeLineEndings') + ); } else { - this.selectToBeginningOfLine(); + const startOfLinePoint = new Point(this.cursor.getScreenRow(), 0); + const prevRange = new Range(startPoint, startOfLinePoint); + this.editor.buffer.setTextInRange( + prevRange, '', pick(options, 'undo', 'normalizeLineEndings') + ); } - this.deleteSelectedText(options); } // Public: Removes the selection or the next character after the start of the @@ -656,6 +678,7 @@ module.exports = class Selection { if (!this.ensureWritable('delete', options)) return; if (this.isEmpty()) this.selectRight(); this.deleteSelectedText(options); + // this._deleteToPreviousPoint( this.cursor.getNextColumnBufferPosition(), options ); } // Public: If the selection is empty, removes all text from the cursor to the @@ -721,6 +744,17 @@ module.exports = class Selection { if (this.cursor) this.cursor.setBufferPosition(bufferRange.start); } + _deleteToPreviousPoint(point, options) { + if (this.isEmpty()) { + const prevRange = new Range( this.getBufferRange().start, point ); + this.editor.buffer.setTextInRange( + prevRange, '', pick(options, 'undo', 'normalizeLineEndings') + ); + } else { + this.deleteSelectedText(options); + } + } + // Public: Removes the line at the beginning of the selection if the selection // is empty unless the selection spans multiple lines in which case all lines // are removed. From 06957af5d744dfaf393daf0a41039aaa2ada1894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Szabo?= Date: Fri, 17 Nov 2023 00:55:13 -0300 Subject: [PATCH 2/8] Fixed most delete commands --- spec/text-editor-spec.js | 108 ++++++++++++++++++++++++++++++++------- src/selection.js | 65 +++++++++++++---------- 2 files changed, 128 insertions(+), 45 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index bc9c73d77..ced074104 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -332,27 +332,99 @@ describe('TextEditor', () => { expect(editor.getCursorBufferPosition()).toEqual([1, 1]); }); - it('emits an event with the old position, new position, and the cursor that moved', () => { - const cursorCallback = jasmine.createSpy('cursor-changed-position'); - const editorCallback = jasmine.createSpy( - 'editor-changed-cursor-position' - ); + describe('listening to cursor movements', () => { + it('emits an event with the old position, new position, and the cursor that moved', () => { + const cursorCallback = jasmine.createSpy('cursor-changed-position'); + const editorCallback = jasmine.createSpy( + 'editor-changed-cursor-position' + ); - editor.getLastCursor().onDidChangePosition(cursorCallback); - editor.onDidChangeCursorPosition(editorCallback); + editor.getLastCursor().onDidChangePosition(cursorCallback); + editor.onDidChangeCursorPosition(editorCallback); - editor.setCursorBufferPosition([2, 4]); + editor.setCursorBufferPosition([2, 4]); - expect(editorCallback).toHaveBeenCalled(); - expect(cursorCallback).toHaveBeenCalled(); - const eventObject = editorCallback.mostRecentCall.args[0]; - expect(cursorCallback.mostRecentCall.args[0]).toEqual(eventObject); + expect(editorCallback).toHaveBeenCalled(); + expect(cursorCallback).toHaveBeenCalled(); + const eventObject = editorCallback.mostRecentCall.args[0]; + expect(cursorCallback.mostRecentCall.args[0]).toEqual(eventObject); - expect(eventObject.oldBufferPosition).toEqual([0, 0]); - expect(eventObject.oldScreenPosition).toEqual([0, 0]); - expect(eventObject.newBufferPosition).toEqual([2, 4]); - expect(eventObject.newScreenPosition).toEqual([2, 4]); - expect(eventObject.cursor).toBe(editor.getLastCursor()); + expect(eventObject.oldBufferPosition).toEqual([0, 0]); + expect(eventObject.oldScreenPosition).toEqual([0, 0]); + expect(eventObject.newBufferPosition).toEqual([2, 4]); + expect(eventObject.newScreenPosition).toEqual([2, 4]); + expect(eventObject.cursor).toBe(editor.getLastCursor()); + }); + + it('emits the event with textChanged: true if text was edited', () => { + let callbacks = [] + let callback = evt => { callbacks.push(evt.textChanged) } + editor.getLastCursor().onDidChangePosition(callback); + editor.onDidChangeCursorPosition(callback); + + editor.insertText('bar') + expect(callbacks).toEqual([true, true]) + + const cmds = [ + 'backspace', + 'deleteToBeginningOfWord', + 'deleteToBeginningOfSubword', + 'deleteToPreviousWordBoundary', + 'deleteToBeginningOfLine', + // 'deleteLine', + // 'joinLines' + ] + cmds.forEach(command => { + editor.setText("HelloWorld!") + editor.setCursorBufferPosition([0, 5]) + callbacks = [] + editor[command].bind(editor)(); + expect(callbacks).toEqual([true, true], `on command ${command}`) + }) + }); + + fit('emits the event with textChanged: true if whole lines were changed', () => { + let callbacks = []; + let callback = evt => { callbacks.push(evt.textChanged) }; + editor.getLastCursor().onDidChangePosition(callback); + editor.onDidChangeCursorPosition(callback); + + editor.setText("HelloWorld!\nGoodbye, world"); + editor.setCursorBufferPosition([0, 5]); + // TODO: Ideally, we want this event to not be called. Unfortunately, + // the world doesn't work like that - there's no way to delete a line + // without changing the current cursor position on TextBuffer. + callbacks = []; + editor.deleteLine(); + // One for the change, and another to reposition the cursor + expect(callbacks).toEqual([true, false], "on command deleteLine") + // 'joinLines' + }) + }); + + it("doesn't emit the event if you deleted something forward", () => { + let callbacks = [] + let callback = evt => { + callbacks.push(evt.textChanged) + } + editor.getLastCursor().onDidChangePosition(callback); + editor.onDidChangeCursorPosition(callback); + + const cmds = [ + 'delete', + 'deleteToEndOfSubword', + 'deleteToEndOfWord', + 'deleteToEndOfLine', + 'deleteToNextWordBoundary' + ] + cmds.forEach(command => { + editor.setText("HelloWorld!") + editor.setCursorBufferPosition([0, 5]) + callbacks = [] + editor[command].bind(editor)(); + expect(callbacks).toEqual([], `on command ${command}`) + }) + }); }); }); @@ -6539,7 +6611,7 @@ describe('TextEditor', () => { }); }); - describe('.deleteLine()', () => { + fdescribe('.deleteLine()', () => { it('deletes the first line when the cursor is there', () => { editor.getLastCursor().moveToTop(); const line1 = buffer.lineForRow(1); diff --git a/src/selection.js b/src/selection.js index cafb2f53f..f5f94f6a8 100644 --- a/src/selection.js +++ b/src/selection.js @@ -594,13 +594,11 @@ module.exports = class Selection { backspace(options = {}) { if (!this.ensureWritable('backspace', options)) return; - const screenPosition = this.cursor.getPreviousColumnScreenPosition() - this._deleteToPreviousPoint( - this.cursor.marker.layer.translateScreenPosition(screenPosition), - options + const screenPosition = this.cursor.getPreviousColumnScreenPosition(); + const bufferPosition = this.cursor.marker.layer.translateScreenPosition( + screenPosition, { clipDirection: 'backward' } ); - // if (this.isEmpty()) this.selectLeft(); - // this.deleteSelectedText(options); + this._deleteToPreviousPoint(bufferPosition, options); } // Public: Removes the selection or, if nothing is selected, then all @@ -611,8 +609,6 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToPreviousWordBoundary(options = {}) { if (!this.ensureWritable('deleteToPreviousWordBoundary', options)) return; - // if (this.isEmpty()) this.selectToPreviousWordBoundary(); - // this.deleteSelectedText(options); this._deleteToPreviousPoint( this.cursor.getPreviousWordBoundaryBufferPosition(), options @@ -627,8 +623,8 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToNextWordBoundary(options = {}) { if (!this.ensureWritable('deleteToNextWordBoundary', options)) return; - if (this.isEmpty()) this.selectToNextWordBoundary(); - this.deleteSelectedText(options); + const position = this.cursor.getNextWordBoundaryBufferPosition(); + this._deleteToNextPoint(position, options); } // Public: Removes from the start of the selection to the beginning of the @@ -676,9 +672,11 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) delete(options = {}) { if (!this.ensureWritable('delete', options)) return; - if (this.isEmpty()) this.selectRight(); - this.deleteSelectedText(options); - // this._deleteToPreviousPoint( this.cursor.getNextColumnBufferPosition(), options ); + const screenPosition = this.cursor.getNextColumnScreenPosition(); + const bufferPosition = this.cursor.marker.layer.translateScreenPosition( + screenPosition, { clipDirection: 'forward' } + ); + this._deleteToNextPoint(bufferPosition, options); } // Public: If the selection is empty, removes all text from the cursor to the @@ -690,14 +688,14 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToEndOfLine(options = {}) { if (!this.ensureWritable('deleteToEndOfLine', options)) return; - if (this.isEmpty()) { - if (this.cursor.isAtEndOfLine()) { - this.delete(options); - return; - } - this.selectToEndOfLine(); + if (this.isEmpty() && this.cursor.isAtEndOfLine()) { + this.delete(options); + } else { + this._deleteToNextPoint( + new Point(this.cursor.getScreenRow(), Infinity), + options + ); } - this.deleteSelectedText(options); } // Public: Removes the selection or all characters from the start of the @@ -707,8 +705,8 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToEndOfWord(options = {}) { if (!this.ensureWritable('deleteToEndOfWord', options)) return; - if (this.isEmpty()) this.selectToEndOfWord(); - this.deleteSelectedText(options); + const position = this.cursor.getEndOfCurrentWordBufferPosition(); + this._deleteToNextPoint(position, options); } // Public: Removes the selection or all characters from the start of the @@ -718,8 +716,8 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToBeginningOfSubword(options = {}) { if (!this.ensureWritable('deleteToBeginningOfSubword', options)) return; - if (this.isEmpty()) this.selectToPreviousSubwordBoundary(); - this.deleteSelectedText(options); + const position = this.cursor.getPreviousWordBoundaryBufferPosition(options); + this._deleteToPreviousPoint(position, options); } // Public: Removes the selection or all characters from the start of the @@ -729,8 +727,10 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToEndOfSubword(options = {}) { if (!this.ensureWritable('deleteToEndOfSubword', options)) return; - if (this.isEmpty()) this.selectToNextSubwordBoundary(); - this.deleteSelectedText(options); + const position = this.cursor.getNextWordBoundaryBufferPosition( + { wordRegex: this.cursor.subwordRegExp() } + ); + this._deleteToNextPoint(position, options); } // Public: Removes only the selected text. @@ -746,7 +746,7 @@ module.exports = class Selection { _deleteToPreviousPoint(point, options) { if (this.isEmpty()) { - const prevRange = new Range( this.getBufferRange().start, point ); + const prevRange = new Range(this.getBufferRange().start, point); this.editor.buffer.setTextInRange( prevRange, '', pick(options, 'undo', 'normalizeLineEndings') ); @@ -755,6 +755,17 @@ module.exports = class Selection { } } + _deleteToNextPoint(point, options) { + if (this.isEmpty()) { + const nextRange = new Range(point, this.getBufferRange().end); + this.editor.buffer.setTextInRange( + nextRange, '', pick(options, 'undo', 'normalizeLineEndings') + ); + } else { + this.deleteSelectedText(options); + } + } + // Public: Removes the line at the beginning of the selection if the selection // is empty unless the selection spans multiple lines in which case all lines // are removed. From df660033c782936f8dfa324093963e251fa24d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Szabo?= Date: Fri, 17 Nov 2023 01:03:46 -0300 Subject: [PATCH 3/8] Fixed (more or less) deleteLine --- src/selection.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/selection.js b/src/selection.js index f5f94f6a8..0f7482ffe 100644 --- a/src/selection.js +++ b/src/selection.js @@ -775,21 +775,23 @@ module.exports = class Selection { deleteLine(options = {}) { if (!this.ensureWritable('deleteLine', options)) return; const range = this.getBufferRange(); - if (range.isEmpty()) { - const start = this.cursor.getScreenRow(); - const range = this.editor.bufferRowsForScreenRows(start, start + 1); - if (range[1] > range[0]) { - this.editor.buffer.deleteRows(range[0], range[1] - 1); + this.editor.transact(() => { + if (range.isEmpty()) { + const start = this.cursor.getScreenRow(); + const range = this.editor.bufferRowsForScreenRows(start, start + 1); + if (range[1] > range[0]) { + this.editor.buffer.deleteRows(range[0], range[1] - 1); + } else { + this.editor.buffer.deleteRow(range[0]); + } } else { - this.editor.buffer.deleteRow(range[0]); + const start = range.start.row; + let end = range.end.row; + if (end !== this.editor.buffer.getLastRow() && range.end.column === 0) + end--; + this.editor.buffer.deleteRows(start, end); } - } else { - const start = range.start.row; - let end = range.end.row; - if (end !== this.editor.buffer.getLastRow() && range.end.column === 0) - end--; - this.editor.buffer.deleteRows(start, end); - } + }) this.cursor.setBufferPosition({ row: this.cursor.getBufferRow(), column: range.start.column From cc365d8b1195dcd82590f1729923f887363f4906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Szabo?= Date: Fri, 17 Nov 2023 01:15:01 -0300 Subject: [PATCH 4/8] Fixed tests --- spec/text-editor-spec.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index ced074104..5f201b469 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -383,7 +383,7 @@ describe('TextEditor', () => { }) }); - fit('emits the event with textChanged: true if whole lines were changed', () => { + it('emits the event with textChanged: true if whole lines were changed', () => { let callbacks = []; let callback = evt => { callbacks.push(evt.textChanged) }; editor.getLastCursor().onDidChangePosition(callback); @@ -397,9 +397,13 @@ describe('TextEditor', () => { callbacks = []; editor.deleteLine(); // One for the change, and another to reposition the cursor - expect(callbacks).toEqual([true, false], "on command deleteLine") - // 'joinLines' - }) + expect(callbacks).toEqual([true, true, false, false], "on command deleteLine") + + editor.setText("HelloWorld!\nGoodbye, world"); + editor.setCursorBufferPosition([0, 5]); + callbacks = []; + editor.joinLines(); + expect(callbacks).toEqual([true, true, false, false], "on command joinLines") }); it("doesn't emit the event if you deleted something forward", () => { @@ -6611,7 +6615,7 @@ describe('TextEditor', () => { }); }); - fdescribe('.deleteLine()', () => { + describe('.deleteLine()', () => { it('deletes the first line when the cursor is there', () => { editor.getLastCursor().moveToTop(); const line1 = buffer.lineForRow(1); From 80cc2e420815ade5a45068cb6945cffc628d71ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Szabo?= Date: Fri, 17 Nov 2023 20:27:47 -0300 Subject: [PATCH 5/8] Removed some comments --- spec/text-editor-spec.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 5f201b469..3f9799a81 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -370,9 +370,7 @@ describe('TextEditor', () => { 'deleteToBeginningOfWord', 'deleteToBeginningOfSubword', 'deleteToPreviousWordBoundary', - 'deleteToBeginningOfLine', - // 'deleteLine', - // 'joinLines' + 'deleteToBeginningOfLine' ] cmds.forEach(command => { editor.setText("HelloWorld!") From 81207050c14ef8da076dd16dc1c26868b971dd96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Szabo?= Date: Fri, 17 Nov 2023 21:53:15 -0300 Subject: [PATCH 6/8] Fixed deleteToBeginningOfSubword --- src/selection.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/selection.js b/src/selection.js index 0f7482ffe..d8e2d2c06 100644 --- a/src/selection.js +++ b/src/selection.js @@ -716,7 +716,9 @@ module.exports = class Selection { // * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false) deleteToBeginningOfSubword(options = {}) { if (!this.ensureWritable('deleteToBeginningOfSubword', options)) return; - const position = this.cursor.getPreviousWordBoundaryBufferPosition(options); + const position = this.cursor.getPreviousWordBoundaryBufferPosition({ + wordRegex: this.cursor.subwordRegExp({ backwards: true }) + }); this._deleteToPreviousPoint(position, options); } From 1fc0dca3bcd4bdd94b7ab12575e2437fac8974e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Szabo?= Date: Fri, 17 Nov 2023 23:10:01 -0300 Subject: [PATCH 7/8] Fixed joinLines --- spec/text-editor-spec.js | 5 ++- src/selection.js | 80 +++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 3f9799a81..b212af5c8 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -401,7 +401,10 @@ describe('TextEditor', () => { editor.setCursorBufferPosition([0, 5]); callbacks = []; editor.joinLines(); - expect(callbacks).toEqual([true, true, false, false], "on command joinLines") + // TODO: Again, not ideal. But still... + // One for moving to the line that will be deleted, one for the actual change + // and one to move to the "join position" between the lines + expect(callbacks).toEqual([false, false, true, true, false, false], "on command joinLines") }); it("doesn't emit the event if you deleted something forward", () => { diff --git a/src/selection.js b/src/selection.js index d8e2d2c06..fa7aa52ca 100644 --- a/src/selection.js +++ b/src/selection.js @@ -810,9 +810,10 @@ module.exports = class Selection { joinLines(options = {}) { if (!this.ensureWritable('joinLines', options)) return; let joinMarker; + const buffer = this.editor.getBuffer(); const selectedRange = this.getBufferRange(); if (selectedRange.isEmpty()) { - if (selectedRange.start.row === this.editor.buffer.getLastRow()) return; + if (selectedRange.start.row === buffer.getLastRow()) return; } else { joinMarker = this.editor.markBufferRange(selectedRange, { invalidate: 'never' @@ -820,45 +821,58 @@ module.exports = class Selection { } const rowCount = Math.max(1, selectedRange.getRowCount() - 1); - for (let i = 0; i < rowCount; i++) { - this.cursor.setBufferPosition([selectedRange.start.row]); - this.cursor.moveToEndOfLine(); + let cursorPositionAfterEdit = null; + buffer.transact(() => { + for (let i = 0; i < rowCount; i++) { + // this.cursor.setBufferPosition([selectedRange.start.row]); + // this.cursor.moveToEndOfLine(); + // + // Remove trailing whitespace from the current line + const scanRange = this.cursor.getCurrentLineBufferRange(); + this.editor.scanInBufferRange(/[ \t]+$/, scanRange, ({ range }) => { + buffer.setTextInRange(range, ''); + }); + const currentRow = selectedRange.start.row; + const nextRow = currentRow + 1; + const haveNextLine = nextRow <= buffer.getLastRow(); + // if (insertSpace) this.insertText(' ', options); + // + // this.cursor.moveToEndOfLine(); + // - // Remove trailing whitespace from the current line - const scanRange = this.cursor.getCurrentLineBufferRange(); - let trailingWhitespaceRange = null; - this.editor.scanInBufferRange(/[ \t]+$/, scanRange, ({ range }) => { - trailingWhitespaceRange = range; - }); - if (trailingWhitespaceRange) { - this.setBufferRange(trailingWhitespaceRange); - this.deleteSelectedText(options); + if(haveNextLine) { + // Remove leading whitespace from the line below + const nextLineRange = this.editor.bufferRangeForBufferRow(nextRow); + this.editor.scanInBufferRange(/^[ \t]+/, nextLineRange, ({ range }) => { + buffer.setTextInRange(range, ''); + }); + + const nextLineIsntEmpty = buffer.lineLengthForRow(nextRow) > 0; + const insertSpace = nextLineIsntEmpty && buffer.lineLengthForRow(currentRow) > 0; + + const endOfLine = new Point(currentRow, Infinity); + const startOfNextLine = new Point(nextRow, 0); + if(i === rowCount - 1) { + // We "trick" the editor into thinking we made a change to where + // the cursor was. Not ideal, but it fixes https://github.com/pulsar-edit/pulsar/issues/802 + this.cursor.setBufferPosition(startOfNextLine); + } + const removeRange = new Range(endOfLine, startOfNextLine); + cursorPositionAfterEdit = + buffer.setTextInRange(removeRange, insertSpace ? ' ' : '').end; + if(nextLineIsntEmpty) { + cursorPositionAfterEdit = cursorPositionAfterEdit.traverse([0, -1]); + } + } } - - const currentRow = selectedRange.start.row; - const nextRow = currentRow + 1; - const insertSpace = - nextRow <= this.editor.buffer.getLastRow() && - this.editor.buffer.lineLengthForRow(nextRow) > 0 && - this.editor.buffer.lineLengthForRow(currentRow) > 0; - if (insertSpace) this.insertText(' ', options); - - this.cursor.moveToEndOfLine(); - - // Remove leading whitespace from the line below - this.modifySelection(() => { - this.cursor.moveRight(); - this.cursor.moveToFirstCharacterOfLine(); - }); - this.deleteSelectedText(options); - - if (insertSpace) this.cursor.moveLeft(); - } + }); if (joinMarker) { const newSelectedRange = joinMarker.getBufferRange(); this.setBufferRange(newSelectedRange); joinMarker.destroy(); + } else if(cursorPositionAfterEdit) { + this.cursor.setBufferPosition(cursorPositionAfterEdit); } } From a873abe611470f053fa3e043255289a4d537b496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Szabo?= Date: Fri, 17 Nov 2023 23:21:31 -0300 Subject: [PATCH 8/8] Removed comments --- src/selection.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/selection.js b/src/selection.js index fa7aa52ca..64a2fbdb8 100644 --- a/src/selection.js +++ b/src/selection.js @@ -824,9 +824,6 @@ module.exports = class Selection { let cursorPositionAfterEdit = null; buffer.transact(() => { for (let i = 0; i < rowCount; i++) { - // this.cursor.setBufferPosition([selectedRange.start.row]); - // this.cursor.moveToEndOfLine(); - // // Remove trailing whitespace from the current line const scanRange = this.cursor.getCurrentLineBufferRange(); this.editor.scanInBufferRange(/[ \t]+$/, scanRange, ({ range }) => { @@ -835,10 +832,6 @@ module.exports = class Selection { const currentRow = selectedRange.start.row; const nextRow = currentRow + 1; const haveNextLine = nextRow <= buffer.getLastRow(); - // if (insertSpace) this.insertText(' ', options); - // - // this.cursor.moveToEndOfLine(); - // if(haveNextLine) { // Remove leading whitespace from the line below