Add `newline_below` action

ForLoveOfCats created

Change summary

assets/keymaps/default.json |  4 +
crates/editor/src/editor.rs | 97 ++++++++++++++++++++++++++++++++++++++
2 files changed, 98 insertions(+), 3 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -124,6 +124,7 @@
         "context": "Editor && mode == full",
         "bindings": {
             "enter": "editor::Newline",
+            "cmd-enter": "editor::NewlineBelow",
             "cmd-f": [
                 "buffer_search::Deploy",
                 {
@@ -141,7 +142,8 @@
     {
         "context": "Editor && mode == auto_height",
         "bindings": {
-            "alt-enter": "editor::Newline"
+            "alt-enter": "editor::Newline",
+            "cmd-alt-enter": "editor::NewlineBelow"
         }
     },
     {

crates/editor/src/editor.rs 🔗

@@ -132,6 +132,7 @@ actions!(
         Backspace,
         Delete,
         Newline,
+        NewlineBelow,
         GoToDiagnostic,
         GoToPrevDiagnostic,
         Indent,
@@ -236,6 +237,7 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(Editor::select);
     cx.add_action(Editor::cancel);
     cx.add_action(Editor::newline);
+    cx.add_action(Editor::newline_below);
     cx.add_action(Editor::backspace);
     cx.add_action(Editor::delete);
     cx.add_action(Editor::tab);
@@ -1915,6 +1917,48 @@ impl Editor {
         });
     }
 
+    pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
+        let buffer = self.buffer.read(cx);
+        let snapshot = buffer.snapshot(cx);
+
+        let mut edits = Vec::new();
+        let mut rows = Vec::new();
+        let mut rows_inserted = 0;
+
+        for selection in self.selections.all_adjusted(cx) {
+            let cursor = selection.head();
+            let row = cursor.row;
+
+            let end_of_line = snapshot
+                .clip_point(Point::new(row, snapshot.line_len(row)), Bias::Left)
+                .to_point(&snapshot);
+
+            let newline = "\n".to_string();
+            edits.push((end_of_line..end_of_line, newline));
+
+            rows_inserted += 1;
+            rows.push(row + rows_inserted);
+        }
+
+        self.transact(cx, |editor, cx| {
+            editor.edit_with_autoindent(edits, cx);
+
+            editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
+                let mut index = 0;
+                s.move_cursors_with(|map, _, _| {
+                    let row = rows[index];
+                    index += 1;
+
+                    let point = Point::new(row, 0);
+                    let boundary = map.next_line_boundary(point).1;
+                    let clipped = map.clip_point(boundary, Bias::Left);
+
+                    (clipped, SelectionGoal::None)
+                });
+            });
+        });
+    }
+
     pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
         let text: Arc<str> = text.into();
         self.transact(cx, |this, cx| {
@@ -2923,8 +2967,8 @@ impl Editor {
         let mut selections = self.selections.all_adjusted(cx);
         let buffer = self.buffer.read(cx);
         let snapshot = buffer.snapshot(cx);
-        let suggested_indents =
-            snapshot.suggested_indents(selections.iter().map(|s| s.head().row), cx);
+        let rows_iter = selections.iter().map(|s| s.head().row);
+        let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
 
         let mut edits = Vec::new();
         let mut prev_edited_row = 0;
@@ -7986,6 +8030,55 @@ mod tests {
         });
     }
 
+    #[gpui::test]
+    async fn test_newline_below(cx: &mut gpui::TestAppContext) {
+        let mut cx = EditorTestContext::new(cx).await;
+        cx.update(|cx| {
+            cx.update_global::<Settings, _, _>(|settings, _| {
+                settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap());
+            });
+        });
+
+        let language = Arc::new(
+            Language::new(
+                LanguageConfig::default(),
+                Some(tree_sitter_rust::language()),
+            )
+            .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
+            .unwrap(),
+        );
+        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+        cx.set_state(indoc! {"
+            const a: |A = (
+                (|
+                    [const_function}(|),
+                    so{m]et[h}ing_|else,|
+                )|
+            |);|
+            "});
+        cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
+        cx.assert_editor_state(indoc! {"
+            const a: A = (
+                |
+                (
+                    |
+                    const_function(),
+                    |
+                    |
+                    something_else,
+                    |
+                    |
+                    |
+                    |
+                )
+                |
+            );
+            |
+            |
+            "});
+    }
+
     #[gpui::test]
     fn test_insert_with_old_selections(cx: &mut gpui::MutableAppContext) {
         cx.set_global(Settings::test(cx));