repl: Add ctrl-alt-enter binding to run in place (#15743)

Kyle Kelley created

Release Notes:

- Added `ctrl-alt-enter` keybinding for `repl::RunInPlace`
(`ctrl-option-enter` on MacOS). Keeps your screen position and cursor in
place when running any block.

Change summary

assets/keymaps/default-linux.json        |  3 ++-
assets/keymaps/default-macos.json        |  3 ++-
crates/quick_action_bar/src/repl_menu.rs |  2 +-
crates/repl/src/repl_editor.rs           |  4 ++--
crates/repl/src/repl_sessions_ui.rs      | 16 +++++++++++++++-
crates/repl/src/session.rs               | 12 +++++++-----
6 files changed, 29 insertions(+), 11 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -479,7 +479,8 @@
   {
     "context": "Editor && jupyter && !ContextEditor",
     "bindings": {
-      "ctrl-shift-enter": "repl::Run"
+      "ctrl-shift-enter": "repl::Run",
+      "ctrl-alt-enter": "repl::RunInPlace"
     }
   },
   {

assets/keymaps/default-macos.json 🔗

@@ -178,7 +178,8 @@
   {
     "context": "Editor && jupyter && !ContextEditor",
     "bindings": {
-      "ctrl-shift-enter": "repl::Run"
+      "ctrl-shift-enter": "repl::Run",
+      "ctrl-alt-enter": "repl::RunInPlace"
     }
   },
   {

crates/quick_action_bar/src/repl_menu.rs 🔗

@@ -134,7 +134,7 @@ impl QuickActionBar {
                         {
                             let editor = editor.clone();
                             move |cx| {
-                                repl::run(editor.clone(), cx).log_err();
+                                repl::run(editor.clone(), true, cx).log_err();
                             }
                         },
                     )

crates/repl/src/repl_editor.rs 🔗

@@ -12,7 +12,7 @@ use crate::repl_store::ReplStore;
 use crate::session::SessionEvent;
 use crate::{KernelSpecification, Session};
 
-pub fn run(editor: WeakView<Editor>, cx: &mut WindowContext) -> Result<()> {
+pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) -> Result<()> {
     let store = ReplStore::global(cx);
     if !store.read(cx).is_enabled() {
         return Ok(());
@@ -89,7 +89,7 @@ pub fn run(editor: WeakView<Editor>, cx: &mut WindowContext) -> Result<()> {
         }
 
         session.update(cx, |session, cx| {
-            session.execute(selected_text, anchor_range, next_cursor, cx);
+            session.execute(selected_text, anchor_range, next_cursor, move_down, cx);
         });
     }
 

crates/repl/src/repl_sessions_ui.rs 🔗

@@ -17,6 +17,7 @@ actions!(
     repl,
     [
         Run,
+        RunInPlace,
         ClearOutputs,
         Sessions,
         Interrupt,
@@ -68,7 +69,20 @@ pub fn init(cx: &mut AppContext) {
                         return;
                     }
 
-                    crate::run(editor_handle.clone(), cx).log_err();
+                    crate::run(editor_handle.clone(), true, cx).log_err();
+                }
+            })
+            .detach();
+
+        editor
+            .register_action({
+                let editor_handle = editor_handle.clone();
+                move |_: &RunInPlace, cx| {
+                    if !JupyterSettings::enabled(cx) {
+                        return;
+                    }
+
+                    crate::run(editor_handle.clone(), false, cx).log_err();
                 }
             })
             .detach();

crates/repl/src/session.rs 🔗

@@ -417,6 +417,7 @@ impl Session {
         code: String,
         anchor_range: Range<Anchor>,
         next_cell: Option<Anchor>,
+        move_down: bool,
         cx: &mut ViewContext<Self>,
     ) {
         let Some(editor) = self.editor.upgrade() else {
@@ -519,12 +520,13 @@ impl Session {
             _ => {}
         }
 
-        // Now move the cursor to after the block
-        editor.update(cx, move |editor, cx| {
-            editor.change_selections(Some(Autoscroll::top_relative(8)), cx, |selections| {
-                selections.select_ranges([new_cursor_pos..new_cursor_pos]);
+        if move_down {
+            editor.update(cx, move |editor, cx| {
+                editor.change_selections(Some(Autoscroll::top_relative(8)), cx, |selections| {
+                    selections.select_ranges([new_cursor_pos..new_cursor_pos]);
+                });
             });
-        });
+        }
     }
 
     fn route(&mut self, message: &JupyterMessage, cx: &mut ViewContext<Self>) {