mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-08-16 22:50:22 +03:00
Merge pull request #810 from pulsar-edit/fix-on-change-cursor-pos
Fix `onDidChangeCursorPosition` callback event property on deleting characters
This commit is contained in:
commit
8c6bef4d07
@ -353,27 +353,104 @@ describe('TextEditor', () => {
|
|||||||
expect(editor.getCursorBufferPosition()).toEqual([1, 1]);
|
expect(editor.getCursorBufferPosition()).toEqual([1, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('emits an event with the old position, new position, and the cursor that moved', () => {
|
describe('listening to cursor movements', () => {
|
||||||
const cursorCallback = jasmine.createSpy('cursor-changed-position');
|
it('emits an event with the old position, new position, and the cursor that moved', () => {
|
||||||
const editorCallback = jasmine.createSpy(
|
const cursorCallback = jasmine.createSpy('cursor-changed-position');
|
||||||
'editor-changed-cursor-position'
|
const editorCallback = jasmine.createSpy(
|
||||||
);
|
'editor-changed-cursor-position'
|
||||||
|
);
|
||||||
|
|
||||||
editor.getLastCursor().onDidChangePosition(cursorCallback);
|
editor.getLastCursor().onDidChangePosition(cursorCallback);
|
||||||
editor.onDidChangeCursorPosition(editorCallback);
|
editor.onDidChangeCursorPosition(editorCallback);
|
||||||
|
|
||||||
editor.setCursorBufferPosition([2, 4]);
|
editor.setCursorBufferPosition([2, 4]);
|
||||||
|
|
||||||
expect(editorCallback).toHaveBeenCalled();
|
expect(editorCallback).toHaveBeenCalled();
|
||||||
expect(cursorCallback).toHaveBeenCalled();
|
expect(cursorCallback).toHaveBeenCalled();
|
||||||
const eventObject = editorCallback.mostRecentCall.args[0];
|
const eventObject = editorCallback.mostRecentCall.args[0];
|
||||||
expect(cursorCallback.mostRecentCall.args[0]).toEqual(eventObject);
|
expect(cursorCallback.mostRecentCall.args[0]).toEqual(eventObject);
|
||||||
|
|
||||||
expect(eventObject.oldBufferPosition).toEqual([0, 0]);
|
expect(eventObject.oldBufferPosition).toEqual([0, 0]);
|
||||||
expect(eventObject.oldScreenPosition).toEqual([0, 0]);
|
expect(eventObject.oldScreenPosition).toEqual([0, 0]);
|
||||||
expect(eventObject.newBufferPosition).toEqual([2, 4]);
|
expect(eventObject.newBufferPosition).toEqual([2, 4]);
|
||||||
expect(eventObject.newScreenPosition).toEqual([2, 4]);
|
expect(eventObject.newScreenPosition).toEqual([2, 4]);
|
||||||
expect(eventObject.cursor).toBe(editor.getLastCursor());
|
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'
|
||||||
|
]
|
||||||
|
cmds.forEach(command => {
|
||||||
|
editor.setText("HelloWorld!")
|
||||||
|
editor.setCursorBufferPosition([0, 5])
|
||||||
|
callbacks = []
|
||||||
|
editor[command].bind(editor)();
|
||||||
|
expect(callbacks).toEqual([true, true], `on command ${command}`)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
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, true, false, false], "on command deleteLine")
|
||||||
|
|
||||||
|
editor.setText("HelloWorld!\nGoodbye, world");
|
||||||
|
editor.setCursorBufferPosition([0, 5]);
|
||||||
|
callbacks = [];
|
||||||
|
editor.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", () => {
|
||||||
|
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}`)
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -300,6 +300,25 @@ module.exports = class Cursor extends Model {
|
|||||||
const range = this.marker.getScreenRange();
|
const range = this.marker.getScreenRange();
|
||||||
if (moveToEndOfSelection && !range.isEmpty()) {
|
if (moveToEndOfSelection && !range.isEmpty()) {
|
||||||
this.setScreenPosition(range.start);
|
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 {
|
} else {
|
||||||
let { row, column } = this.getScreenPosition();
|
let { row, column } = this.getScreenPosition();
|
||||||
|
|
||||||
@ -310,7 +329,7 @@ module.exports = class Cursor extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
column = column - columnCount;
|
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();
|
const range = this.marker.getScreenRange();
|
||||||
if (moveToEndOfSelection && !range.isEmpty()) {
|
if (moveToEndOfSelection && !range.isEmpty()) {
|
||||||
this.setScreenPosition(range.end);
|
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 {
|
} else {
|
||||||
let { row, column } = this.getScreenPosition();
|
let { row, column } = this.getScreenPosition();
|
||||||
const maxLines = this.editor.getScreenLineCount();
|
const maxLines = this.editor.getScreenLineCount();
|
||||||
@ -340,7 +378,7 @@ module.exports = class Cursor extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
column = column + columnCount;
|
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
|
// * `allowPrevious` A {Boolean} indicating whether the beginning of the
|
||||||
// previous word can be returned.
|
// previous word can be returned.
|
||||||
//
|
//
|
||||||
// Returns a {Range}.
|
// Returns a {Point}.
|
||||||
getBeginningOfCurrentWordBufferPosition(options = {}) {
|
getBeginningOfCurrentWordBufferPosition(options = {}) {
|
||||||
const allowPrevious = options.allowPrevious !== false;
|
const allowPrevious = options.allowPrevious !== false;
|
||||||
const position = this.getBufferPosition();
|
const position = this.getBufferPosition();
|
||||||
@ -629,7 +667,7 @@ module.exports = class Cursor extends Model {
|
|||||||
// * `wordRegex` A {RegExp} indicating what constitutes a "word"
|
// * `wordRegex` A {RegExp} indicating what constitutes a "word"
|
||||||
// (default: {::wordRegExp}).
|
// (default: {::wordRegExp}).
|
||||||
//
|
//
|
||||||
// Returns a {Range}
|
// Returns a {Point}
|
||||||
getBeginningOfNextWordBufferPosition(options = {}) {
|
getBeginningOfNextWordBufferPosition(options = {}) {
|
||||||
const currentBufferPosition = this.getBufferPosition();
|
const currentBufferPosition = this.getBufferPosition();
|
||||||
const start = this.isInsideWord(options)
|
const start = this.isInsideWord(options)
|
||||||
@ -674,6 +712,8 @@ module.exports = class Cursor extends Model {
|
|||||||
// * `options` (optional) {Object}
|
// * `options` (optional) {Object}
|
||||||
// * `includeNewline` A {Boolean} which controls whether the Range should
|
// * `includeNewline` A {Boolean} which controls whether the Range should
|
||||||
// include the newline.
|
// include the newline.
|
||||||
|
//
|
||||||
|
// Returns a {Range}.
|
||||||
getCurrentLineBufferRange(options) {
|
getCurrentLineBufferRange(options) {
|
||||||
return this.editor.bufferRangeForBufferRow(this.getBufferRow(), options);
|
return this.editor.bufferRangeForBufferRow(this.getBufferRow(), options);
|
||||||
}
|
}
|
||||||
|
200
src/selection.js
200
src/selection.js
@ -593,8 +593,12 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
backspace(options = {}) {
|
backspace(options = {}) {
|
||||||
if (!this.ensureWritable('backspace', options)) return;
|
if (!this.ensureWritable('backspace', options)) return;
|
||||||
if (this.isEmpty()) this.selectLeft();
|
|
||||||
this.deleteSelectedText(options);
|
const screenPosition = this.cursor.getPreviousColumnScreenPosition();
|
||||||
|
const bufferPosition = this.cursor.marker.layer.translateScreenPosition(
|
||||||
|
screenPosition, { clipDirection: 'backward' }
|
||||||
|
);
|
||||||
|
this._deleteToPreviousPoint(bufferPosition, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public: Removes the selection or, if nothing is selected, then all
|
// Public: Removes the selection or, if nothing is selected, then all
|
||||||
@ -605,8 +609,10 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
deleteToPreviousWordBoundary(options = {}) {
|
deleteToPreviousWordBoundary(options = {}) {
|
||||||
if (!this.ensureWritable('deleteToPreviousWordBoundary', options)) return;
|
if (!this.ensureWritable('deleteToPreviousWordBoundary', options)) return;
|
||||||
if (this.isEmpty()) this.selectToPreviousWordBoundary();
|
this._deleteToPreviousPoint(
|
||||||
this.deleteSelectedText(options);
|
this.cursor.getPreviousWordBoundaryBufferPosition(),
|
||||||
|
options
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public: Removes the selection or, if nothing is selected, then all
|
// Public: Removes the selection or, if nothing is selected, then all
|
||||||
@ -617,8 +623,8 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
deleteToNextWordBoundary(options = {}) {
|
deleteToNextWordBoundary(options = {}) {
|
||||||
if (!this.ensureWritable('deleteToNextWordBoundary', options)) return;
|
if (!this.ensureWritable('deleteToNextWordBoundary', options)) return;
|
||||||
if (this.isEmpty()) this.selectToNextWordBoundary();
|
const position = this.cursor.getNextWordBoundaryBufferPosition();
|
||||||
this.deleteSelectedText(options);
|
this._deleteToNextPoint(position, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public: Removes from the start of the selection to the beginning of the
|
// Public: Removes from the start of the selection to the beginning of the
|
||||||
@ -628,8 +634,10 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
deleteToBeginningOfWord(options = {}) {
|
deleteToBeginningOfWord(options = {}) {
|
||||||
if (!this.ensureWritable('deleteToBeginningOfWord', options)) return;
|
if (!this.ensureWritable('deleteToBeginningOfWord', options)) return;
|
||||||
if (this.isEmpty()) this.selectToBeginningOfWord();
|
this._deleteToPreviousPoint(
|
||||||
this.deleteSelectedText(options);
|
this.cursor.getBeginningOfCurrentWordBufferPosition(),
|
||||||
|
options
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public: Removes from the beginning of the line which the selection begins on
|
// Public: Removes from the beginning of the line which the selection begins on
|
||||||
@ -639,12 +647,22 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
deleteToBeginningOfLine(options = {}) {
|
deleteToBeginningOfLine(options = {}) {
|
||||||
if (!this.ensureWritable('deleteToBeginningOfLine', options)) return;
|
if (!this.ensureWritable('deleteToBeginningOfLine', options)) return;
|
||||||
|
const startPoint = this.getBufferRange().start;
|
||||||
if (this.isEmpty() && this.cursor.isAtBeginningOfLine()) {
|
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 {
|
} 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
|
// Public: Removes the selection or the next character after the start of the
|
||||||
@ -654,8 +672,11 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
delete(options = {}) {
|
delete(options = {}) {
|
||||||
if (!this.ensureWritable('delete', options)) return;
|
if (!this.ensureWritable('delete', options)) return;
|
||||||
if (this.isEmpty()) this.selectRight();
|
const screenPosition = this.cursor.getNextColumnScreenPosition();
|
||||||
this.deleteSelectedText(options);
|
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
|
// Public: If the selection is empty, removes all text from the cursor to the
|
||||||
@ -667,14 +688,14 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
deleteToEndOfLine(options = {}) {
|
deleteToEndOfLine(options = {}) {
|
||||||
if (!this.ensureWritable('deleteToEndOfLine', options)) return;
|
if (!this.ensureWritable('deleteToEndOfLine', options)) return;
|
||||||
if (this.isEmpty()) {
|
if (this.isEmpty() && this.cursor.isAtEndOfLine()) {
|
||||||
if (this.cursor.isAtEndOfLine()) {
|
this.delete(options);
|
||||||
this.delete(options);
|
} else {
|
||||||
return;
|
this._deleteToNextPoint(
|
||||||
}
|
new Point(this.cursor.getScreenRow(), Infinity),
|
||||||
this.selectToEndOfLine();
|
options
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.deleteSelectedText(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public: Removes the selection or all characters from the start of the
|
// Public: Removes the selection or all characters from the start of the
|
||||||
@ -684,8 +705,8 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
deleteToEndOfWord(options = {}) {
|
deleteToEndOfWord(options = {}) {
|
||||||
if (!this.ensureWritable('deleteToEndOfWord', options)) return;
|
if (!this.ensureWritable('deleteToEndOfWord', options)) return;
|
||||||
if (this.isEmpty()) this.selectToEndOfWord();
|
const position = this.cursor.getEndOfCurrentWordBufferPosition();
|
||||||
this.deleteSelectedText(options);
|
this._deleteToNextPoint(position, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public: Removes the selection or all characters from the start of the
|
// Public: Removes the selection or all characters from the start of the
|
||||||
@ -695,8 +716,10 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
deleteToBeginningOfSubword(options = {}) {
|
deleteToBeginningOfSubword(options = {}) {
|
||||||
if (!this.ensureWritable('deleteToBeginningOfSubword', options)) return;
|
if (!this.ensureWritable('deleteToBeginningOfSubword', options)) return;
|
||||||
if (this.isEmpty()) this.selectToPreviousSubwordBoundary();
|
const position = this.cursor.getPreviousWordBoundaryBufferPosition({
|
||||||
this.deleteSelectedText(options);
|
wordRegex: this.cursor.subwordRegExp({ backwards: true })
|
||||||
|
});
|
||||||
|
this._deleteToPreviousPoint(position, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public: Removes the selection or all characters from the start of the
|
// Public: Removes the selection or all characters from the start of the
|
||||||
@ -706,8 +729,10 @@ module.exports = class Selection {
|
|||||||
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
// * `bypassReadOnly` (optional) {Boolean} Must be `true` to modify text within a read-only editor. (default: false)
|
||||||
deleteToEndOfSubword(options = {}) {
|
deleteToEndOfSubword(options = {}) {
|
||||||
if (!this.ensureWritable('deleteToEndOfSubword', options)) return;
|
if (!this.ensureWritable('deleteToEndOfSubword', options)) return;
|
||||||
if (this.isEmpty()) this.selectToNextSubwordBoundary();
|
const position = this.cursor.getNextWordBoundaryBufferPosition(
|
||||||
this.deleteSelectedText(options);
|
{ wordRegex: this.cursor.subwordRegExp() }
|
||||||
|
);
|
||||||
|
this._deleteToNextPoint(position, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public: Removes only the selected text.
|
// Public: Removes only the selected text.
|
||||||
@ -721,6 +746,28 @@ module.exports = class Selection {
|
|||||||
if (this.cursor) this.cursor.setBufferPosition(bufferRange.start);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_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
|
// 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
|
// is empty unless the selection spans multiple lines in which case all lines
|
||||||
// are removed.
|
// are removed.
|
||||||
@ -730,21 +777,23 @@ module.exports = class Selection {
|
|||||||
deleteLine(options = {}) {
|
deleteLine(options = {}) {
|
||||||
if (!this.ensureWritable('deleteLine', options)) return;
|
if (!this.ensureWritable('deleteLine', options)) return;
|
||||||
const range = this.getBufferRange();
|
const range = this.getBufferRange();
|
||||||
if (range.isEmpty()) {
|
this.editor.transact(() => {
|
||||||
const start = this.cursor.getScreenRow();
|
if (range.isEmpty()) {
|
||||||
const range = this.editor.bufferRowsForScreenRows(start, start + 1);
|
const start = this.cursor.getScreenRow();
|
||||||
if (range[1] > range[0]) {
|
const range = this.editor.bufferRowsForScreenRows(start, start + 1);
|
||||||
this.editor.buffer.deleteRows(range[0], range[1] - 1);
|
if (range[1] > range[0]) {
|
||||||
|
this.editor.buffer.deleteRows(range[0], range[1] - 1);
|
||||||
|
} else {
|
||||||
|
this.editor.buffer.deleteRow(range[0]);
|
||||||
|
}
|
||||||
} else {
|
} 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({
|
this.cursor.setBufferPosition({
|
||||||
row: this.cursor.getBufferRow(),
|
row: this.cursor.getBufferRow(),
|
||||||
column: range.start.column
|
column: range.start.column
|
||||||
@ -761,9 +810,10 @@ module.exports = class Selection {
|
|||||||
joinLines(options = {}) {
|
joinLines(options = {}) {
|
||||||
if (!this.ensureWritable('joinLines', options)) return;
|
if (!this.ensureWritable('joinLines', options)) return;
|
||||||
let joinMarker;
|
let joinMarker;
|
||||||
|
const buffer = this.editor.getBuffer();
|
||||||
const selectedRange = this.getBufferRange();
|
const selectedRange = this.getBufferRange();
|
||||||
if (selectedRange.isEmpty()) {
|
if (selectedRange.isEmpty()) {
|
||||||
if (selectedRange.start.row === this.editor.buffer.getLastRow()) return;
|
if (selectedRange.start.row === buffer.getLastRow()) return;
|
||||||
} else {
|
} else {
|
||||||
joinMarker = this.editor.markBufferRange(selectedRange, {
|
joinMarker = this.editor.markBufferRange(selectedRange, {
|
||||||
invalidate: 'never'
|
invalidate: 'never'
|
||||||
@ -771,45 +821,51 @@ module.exports = class Selection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rowCount = Math.max(1, selectedRange.getRowCount() - 1);
|
const rowCount = Math.max(1, selectedRange.getRowCount() - 1);
|
||||||
for (let i = 0; i < rowCount; i++) {
|
let cursorPositionAfterEdit = null;
|
||||||
this.cursor.setBufferPosition([selectedRange.start.row]);
|
buffer.transact(() => {
|
||||||
this.cursor.moveToEndOfLine();
|
for (let i = 0; i < rowCount; i++) {
|
||||||
|
// 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();
|
||||||
|
|
||||||
// Remove trailing whitespace from the current line
|
if(haveNextLine) {
|
||||||
const scanRange = this.cursor.getCurrentLineBufferRange();
|
// Remove leading whitespace from the line below
|
||||||
let trailingWhitespaceRange = null;
|
const nextLineRange = this.editor.bufferRangeForBufferRow(nextRow);
|
||||||
this.editor.scanInBufferRange(/[ \t]+$/, scanRange, ({ range }) => {
|
this.editor.scanInBufferRange(/^[ \t]+/, nextLineRange, ({ range }) => {
|
||||||
trailingWhitespaceRange = range;
|
buffer.setTextInRange(range, '');
|
||||||
});
|
});
|
||||||
if (trailingWhitespaceRange) {
|
|
||||||
this.setBufferRange(trailingWhitespaceRange);
|
const nextLineIsntEmpty = buffer.lineLengthForRow(nextRow) > 0;
|
||||||
this.deleteSelectedText(options);
|
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) {
|
if (joinMarker) {
|
||||||
const newSelectedRange = joinMarker.getBufferRange();
|
const newSelectedRange = joinMarker.getBufferRange();
|
||||||
this.setBufferRange(newSelectedRange);
|
this.setBufferRange(newSelectedRange);
|
||||||
joinMarker.destroy();
|
joinMarker.destroy();
|
||||||
|
} else if(cursorPositionAfterEdit) {
|
||||||
|
this.cursor.setBufferPosition(cursorPositionAfterEdit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user