-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Product Backlog - Move Line Up/Down on Key #1282
Changes from 2 commits
b86ec3f
bed03b5
53ed4f0
4833b4c
df6a3b7
45034aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -152,6 +152,85 @@ define(function (require, exports, module) { | |
var selectedText = doc.getRange(sel.start, sel.end) + delimiter; | ||
doc.replaceRange(selectedText, sel.start); | ||
} | ||
|
||
/** | ||
* Moves the selected text, or current line if no selection, one line up. The cursor/selection | ||
* moves with the line/lines. | ||
*/ | ||
function moveLineUp(editor) { | ||
editor = editor || EditorManager.getFocusedEditor(); | ||
if (!editor) { | ||
return; | ||
} | ||
|
||
var sel = editor.getSelection(), | ||
hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch); | ||
|
||
sel.start.ch = 0; | ||
// The end of the selection becomes the start of the next line, if it isn't already | ||
if (!hasSelection || sel.end.ch !== 0) { | ||
sel.end = {line: sel.end.line + 1, ch: 0}; | ||
} | ||
|
||
if (sel.start.line !== 0) { | ||
// Make the edit | ||
var doc = editor.document; | ||
|
||
var currentText = doc.getRange(sel.start, sel.end); | ||
var prevText = doc.getRange({ line: sel.start.line - 1, ch: 0 }, sel.start); | ||
|
||
if (sel.end.line === editor.lineCount()) { | ||
currentText += "\n"; | ||
prevText = prevText.substring(0, prevText.length - 1); | ||
} | ||
|
||
doc.replaceRange(currentText, { line: sel.start.line - 1, ch: 0 }, | ||
{ line: sel.end.line - 1, ch: 0 }); | ||
doc.replaceRange(prevText, { line: sel.end.line - 1, ch: 0 }, sel.end); | ||
editor.setSelection({ line: sel.start.line - 1, ch: 0 }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two questions:
You'd still need a special case for the bottommost line, but I think you could handle it just by adjusting the deletion range. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{ line: sel.end.line - 1, ch: 0 }); | ||
} | ||
} | ||
|
||
/** | ||
* Moves the selected text, or current line if no selection, one line down. The cursor/selection | ||
* moves with the line/lines. | ||
*/ | ||
function moveLineDown(editor) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code here is very close to moveLineUp(). Is there a clean way to consolidate these? For the parts that just vary +1 vs. -1, you could pass in a "direction" argument. Then the only real diffs would be the corner case checks. |
||
editor = editor || EditorManager.getFocusedEditor(); | ||
if (!editor) { | ||
return; | ||
} | ||
|
||
var sel = editor.getSelection(), | ||
hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch); | ||
|
||
sel.start.ch = 0; | ||
// The end of the selection becomes the start of the next line, if it isn't already | ||
if (!hasSelection || sel.end.ch !== 0) { | ||
sel.end = {line: sel.end.line + 1, ch: 0}; | ||
} | ||
|
||
if (sel.end.line < editor.lineCount()) { | ||
|
||
// Make the edit | ||
var doc = editor.document; | ||
|
||
var currentText = doc.getRange(sel.start, sel.end); | ||
var nextText = doc.getRange(sel.end, { line: sel.end.line + 1, ch: 0 }); | ||
|
||
if (sel.end.line === editor.lineCount() - 1) { | ||
currentText = currentText.substring(0, currentText.length - 1); | ||
nextText += "\n"; | ||
} | ||
|
||
doc.replaceRange(currentText, { line: sel.start.line + 1, ch: 0 }, | ||
{ line: sel.end.line + 1, ch: 0 }); | ||
doc.replaceRange(nextText, sel.start, { line: sel.start.line + 1, ch: 0 }); | ||
editor.setSelection({ line: sel.start.line + 1, ch: 0 }, | ||
{ line: sel.end.line + 1, ch: 0 }); | ||
} | ||
} | ||
|
||
/** | ||
* Indent a line of text if no selection. Otherwise, indent all lines in selection. | ||
|
@@ -182,4 +261,6 @@ define(function (require, exports, module) { | |
CommandManager.register(Strings.CMD_UNINDENT, Commands.EDIT_UNINDENT, unidentText); | ||
CommandManager.register(Strings.CMD_COMMENT, Commands.EDIT_LINE_COMMENT, lineComment); | ||
CommandManager.register(Strings.CMD_DUPLICATE, Commands.EDIT_DUPLICATE, duplicateText); | ||
CommandManager.register(Strings.CMD_LINE_UP, Commands.EDIT_LINE_UP, moveLineUp); | ||
CommandManager.register(Strings.CMD_LINE_DOWN, Commands.EDIT_LINE_DOWN, moveLineDown); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -500,6 +500,248 @@ define(function (require, exports, module) { | |
}); | ||
}); | ||
|
||
describe("Move Lines Up/Down", function () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are great! Very thorough. One more area to cover, though: please add tests for the first/last-line edge cases (moving the bottommost line up or the topmost line down). If I comment out the inner There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's weird, moving up the bottom line works well for me. Anyway, I will add the tests for those edge cases. |
||
|
||
it("should move whole line up if no selection", function () { | ||
// place cursor in middle of line 1 | ||
myEditor.setCursorPos(1, 10); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_UP, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[0]; | ||
lines[0] = lines[1]; | ||
lines[1] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 0, ch: 0}, end: {line: 1, ch: 0}}); | ||
}); | ||
|
||
it("should move whole line down if no selection", function () { | ||
// place cursor in middle of line 1 | ||
myEditor.setCursorPos(1, 10); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_DOWN, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[2]; | ||
lines[2] = lines[1]; | ||
lines[1] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 2, ch: 0}, end: {line: 3, ch: 0}}); | ||
}); | ||
|
||
it("shouldn't move up first line", function () { | ||
// place cursor at start of line 0 | ||
myEditor.setCursorPos(0, 0); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_UP, myEditor); | ||
|
||
expect(myDocument.getText()).toEqual(defaultContent); | ||
expectCursorAt({line: 0, ch: 0}); | ||
}); | ||
|
||
it("shouldn't move line down if selected line is at end of file", function () { | ||
var lines = defaultContent.split("\n"), | ||
len = lines.length; | ||
|
||
// place cursor at the beginning of the last line | ||
myEditor.setCursorPos(len-1, 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JSLint nit: need spaces around the "-". Same thing with the last line of this function. |
||
|
||
CommandManager.execute(Commands.EDIT_LINE_DOWN, myEditor); | ||
|
||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectCursorAt({line: len-1, ch: 0}); | ||
}); | ||
|
||
it("should move up empty line", function () { | ||
// place cursor on line 6 | ||
myEditor.setCursorPos(6, 0); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_UP, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[5]; | ||
lines[5] = lines[6]; | ||
lines[6] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 5, ch: 0}, end: {line: 6, ch: 0}}); | ||
}); | ||
|
||
it("should move down empty line", function () { | ||
// place cursor on line 6 | ||
myEditor.setCursorPos(6, 0); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_DOWN, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[7]; | ||
lines[7] = lines[6]; | ||
lines[6] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectCursorAt({line: 7, ch: 0}); | ||
}); | ||
|
||
it("should move up when entire line selected, excluding newline", function () { | ||
// select all of line 1, EXcluding trailing \n | ||
myEditor.setSelection({line: 1, ch: 0}, {line: 1, ch: 20}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_UP, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[0]; | ||
lines[0] = lines[1]; | ||
lines[1] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 0, ch: 0}, end: {line: 1, ch: 0}}); | ||
}); | ||
|
||
it("should move down when entire line selected, excluding newline", function () { | ||
// select all of line 1, EXcluding trailing \n | ||
myEditor.setSelection({line: 1, ch: 0}, {line: 1, ch: 20}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_DOWN, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[2]; | ||
lines[2] = lines[1]; | ||
lines[1] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 2, ch: 0}, end: {line: 3, ch: 0}}); | ||
}); | ||
|
||
it("should move up when entire line selected, including newline", function () { | ||
// select all of line 1, INcluding trailing \n | ||
myEditor.setSelection({line: 1, ch: 0}, {line: 2, ch: 0}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_UP, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[0]; | ||
lines[0] = lines[1]; | ||
lines[1] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 0, ch: 0}, end: {line: 1, ch: 0}}); | ||
}); | ||
|
||
it("should move down when entire line selected, including newline", function () { | ||
// select all of line 1, INcluding trailing \n | ||
myEditor.setSelection({line: 1, ch: 0}, {line: 2, ch: 0}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_DOWN, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[2]; | ||
lines[2] = lines[1]; | ||
lines[1] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 2, ch: 0}, end: {line: 3, ch: 0}}); | ||
}); | ||
|
||
it("should move up when multiple lines selected", function () { | ||
// select lines 2-3 | ||
myEditor.setSelection({line: 2, ch: 0}, {line: 4, ch: 0}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_UP, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[1]; | ||
lines[1] = lines[2]; | ||
lines[2] = lines[3]; | ||
lines[3] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 1, ch: 0}, end: {line: 3, ch: 0}}); | ||
}); | ||
|
||
it("should move down when multiple lines selected", function () { | ||
// select lines 2-3 | ||
myEditor.setSelection({line: 2, ch: 0}, {line: 4, ch: 0}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_DOWN, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[4]; | ||
lines[4] = lines[3]; | ||
lines[3] = lines[2]; | ||
lines[2] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 3, ch: 0}, end: {line: 5, ch: 0}}); | ||
}); | ||
|
||
it("should move up selection crossing line boundary", function () { | ||
// select from middle of line 2 to middle of line 3 | ||
myEditor.setSelection({line: 2, ch: 13}, {line: 3, ch: 11}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_UP, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[1]; | ||
lines[1] = lines[2]; | ||
lines[2] = lines[3]; | ||
lines[3] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 1, ch: 0}, end: {line: 3, ch: 0}}); | ||
}); | ||
|
||
it("should move down selection crossing line boundary", function () { | ||
// select from middle of line 2 to middle of line 3 | ||
myEditor.setSelection({line: 2, ch: 13}, {line: 3, ch: 11}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_DOWN, myEditor); | ||
|
||
var lines = defaultContent.split("\n"); | ||
var temp = lines[4]; | ||
lines[4] = lines[3]; | ||
lines[3] = lines[2]; | ||
lines[2] = temp; | ||
var expectedText = lines.join("\n"); | ||
|
||
expect(myDocument.getText()).toEqual(expectedText); | ||
expectSelection({start: {line: 3, ch: 0}, end: {line: 5, ch: 0}}); | ||
}); | ||
|
||
it("shouldn't move up after select all", function () { | ||
myEditor.setSelection({line: 0, ch: 0}, {line: 7, ch: 1}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_UP, myEditor); | ||
|
||
expect(myDocument.getText()).toEqual(defaultContent); | ||
expectSelection({start: {line: 0, ch: 0}, end: {line: 7, ch: 1}}); | ||
}); | ||
|
||
it("shouldn't move down after select all", function () { | ||
myEditor.setSelection({line: 0, ch: 0}, {line: 7, ch: 1}); | ||
|
||
CommandManager.execute(Commands.EDIT_LINE_DOWN, myEditor); | ||
|
||
expect(myDocument.getText()).toEqual(defaultContent); | ||
expectSelection({start: {line: 0, ch: 0}, end: {line: 7, ch: 1}}); | ||
}); | ||
}); | ||
|
||
|
||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The standard on Windows seems to be Ctrl+Shift+up/down (see Sublime, Notepad++, and IntelliJ/WebStorm). The only odd one out I could find is Eclipse, which uses Alt+up/down. On all of those editors, Ctrl+up/down instead scrolls the view by one line (without making any edits).
On Mac, most editors seem to lack this feature. Eclipse uses Alt+up/down, Sublime uses Cmd+Ctrl+up/down (edit: TextMate and Espresso also use this shortcut), and IntelliJ/WebStorm uses Cmd+Shift+up/down. I'd be inclined to go with Cmd+Shift since it's more consistent with the Windows shortcut (in fact Brackets will automatically convert Ctrl<->Cmd, so you wouldn't even have to declare two shortcuts in that case).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re the menus: move line up/down feels more related to Duplicate. In fact, now that this menu section is getting longer it feels weird to have Comment/Uncomment grouped in with more generic editing commands. How about reordering it so the menu looks like this?
...
Duplicate
Move Line(s) Up
Move Line(s) Down
---divider---
Comment/Uncomment Lines
---divider---
Use Tab Characters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the note in the backlog got this shortcut confused with the "scroll 1 line" feature that most editors have. Let's use Ctrl+Shift+up/down on Windows.
On Mac, I found that more of them support this than I thought. Sublime, TextMate and Espresso all use Cmd+Ctrl+up/down, so that seems like the best shortcut to use.