Detailed changes
@@ -315,6 +315,18 @@
"cmd-ctrl-p": "editor::AddSelectionAbove",
"cmd-alt-down": "editor::AddSelectionBelow",
"cmd-ctrl-n": "editor::AddSelectionBelow",
+ "cmd-shift-k": "editor::DeleteLine",
+ "alt-up": "editor::MoveLineUp",
+ "alt-down": "editor::MoveLineDown",
+ "alt-shift-up": [
+ "editor::DuplicateLine",
+ {
+ "move_upwards": true
+ }
+ ],
+ "alt-shift-down": "editor::DuplicateLine",
+ "ctrl-shift-right": "editor::SelectLargerSyntaxNode",
+ "ctrl-shift-left": "editor::SelectSmallerSyntaxNode",
"cmd-d": [
"editor::SelectNext",
{
@@ -347,8 +359,6 @@
"advance_downwards": false
}
],
- "alt-up": "editor::SelectLargerSyntaxNode",
- "alt-down": "editor::SelectSmallerSyntaxNode",
"cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic",
@@ -454,11 +464,7 @@
{
"context": "Editor",
"bindings": {
- "ctrl-shift-k": "editor::DeleteLine",
- "cmd-shift-d": "editor::DuplicateLine",
"ctrl-j": "editor::JoinLines",
- "ctrl-cmd-up": "editor::MoveLineUp",
- "ctrl-cmd-down": "editor::MoveLineDown",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
@@ -39,6 +39,8 @@
"advance_downwards": true
}
],
+ "alt-up": "editor::SelectLargerSyntaxNode",
+ "alt-down": "editor::SelectSmallerSyntaxNode",
"shift-alt-up": "editor::MoveLineUp",
"shift-alt-down": "editor::MoveLineDown",
"cmd-alt-l": "editor::Format",
@@ -94,6 +94,12 @@ pub struct SelectDownByLines {
pub(super) lines: u32,
}
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct DuplicateLine {
+ #[serde(default)]
+ pub move_upwards: bool,
+}
+
impl_actions!(
editor,
[
@@ -112,7 +118,8 @@ impl_actions!(
MoveUpByLines,
MoveDownByLines,
SelectUpByLines,
- SelectDownByLines
+ SelectDownByLines,
+ DuplicateLine
]
);
@@ -152,7 +159,6 @@ gpui::actions!(
DeleteToPreviousSubwordStart,
DeleteToPreviousWordStart,
DisplayCursorNames,
- DuplicateLine,
ExpandMacroRecursively,
FindAllReferences,
Fold,
@@ -5032,7 +5032,7 @@ impl Editor {
});
}
- pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
+ pub fn duplicate_line(&mut self, action: &DuplicateLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let selections = self.selections.all::<Point>(cx);
@@ -5053,14 +5053,20 @@ impl Editor {
}
}
- // Copy the text from the selected row region and splice it at the start of the region.
+ // Copy the text from the selected row region and splice it either at the start
+ // or end of the region.
let start = Point::new(rows.start, 0);
let end = Point::new(rows.end - 1, buffer.line_len(rows.end - 1));
let text = buffer
.text_for_range(start..end)
.chain(Some("\n"))
.collect::<String>();
- edits.push((start..start, text));
+ let insert_location = if action.move_upwards {
+ Point::new(rows.end, 0)
+ } else {
+ start
+ };
+ edits.push((insert_location..insert_location, text));
}
self.transact(cx, |this, cx| {
@@ -3118,7 +3118,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
])
});
- view.duplicate_line(&DuplicateLine, cx);
+ view.duplicate_line(&DuplicateLine::default(), cx);
assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
assert_eq!(
view.selections.display_ranges(cx),
@@ -3142,7 +3142,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
])
});
- view.duplicate_line(&DuplicateLine, cx);
+ view.duplicate_line(&DuplicateLine::default(), cx);
assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
assert_eq!(
view.selections.display_ranges(cx),
@@ -3152,6 +3152,56 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
]
);
});
+
+ // With `move_upwards` the selections stay in place, except for
+ // the lines inserted above them
+ let view = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ });
+ _ = view.update(cx, |view, cx| {
+ view.change_selections(None, cx, |s| {
+ s.select_display_ranges([
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+ ])
+ });
+ view.duplicate_line(&DuplicateLine { move_upwards: true }, cx);
+ assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
+ assert_eq!(
+ view.selections.display_ranges(cx),
+ vec![
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
+ DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
+ ]
+ );
+ });
+
+ let view = cx.add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ });
+ _ = view.update(cx, |view, cx| {
+ view.change_selections(None, cx, |s| {
+ s.select_display_ranges([
+ DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
+ ])
+ });
+ view.duplicate_line(&DuplicateLine { move_upwards: true }, cx);
+ assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
+ assert_eq!(
+ view.selections.display_ranges(cx),
+ vec![
+ DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
+ ]
+ );
+ });
}
#[gpui::test]
@@ -99,7 +99,10 @@ pub fn app_menus() -> Vec<Menu<'static>> {
MenuItem::separator(),
MenuItem::action("Move Line Up", editor::actions::MoveLineUp),
MenuItem::action("Move Line Down", editor::actions::MoveLineDown),
- MenuItem::action("Duplicate Selection", editor::actions::DuplicateLine),
+ MenuItem::action(
+ "Duplicate Selection",
+ editor::actions::DuplicateLine::default(),
+ ),
],
},
Menu {