diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 02d05c0409f1f..988172071764d 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -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", diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 5fb9bf7e3228c..c1998d6cd125b 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -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", diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index d0bbab0335967..018718943f56e 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -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, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index deaae276780e6..8a931619c9d31 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5032,7 +5032,7 @@ impl Editor { }); } - pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { + pub fn duplicate_line(&mut self, action: &DuplicateLine, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; let selections = self.selections.all::(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::(); - 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| { diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 36daaec5d9427..8ea1c11eb4e46 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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] diff --git a/crates/zed/src/app_menus.rs b/crates/zed/src/app_menus.rs index cde567318c672..9f5c970d4db5f 100644 --- a/crates/zed/src/app_menus.rs +++ b/crates/zed/src/app_menus.rs @@ -99,7 +99,10 @@ pub fn app_menus() -> Vec> { 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 {